Revert "Add roocode.com website to monorepo" (#4145)
Revert "Add roocode.com website to monorepo (#4128)"
This reverts commit 0f22eabb1b.
3
.vscode/extensions.json
vendored
|
|
@ -6,7 +6,6 @@
|
|||
"esbenp.prettier-vscode",
|
||||
"csstools.postcss",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"connor4312.esbuild-problem-matchers",
|
||||
"yoavbls.pretty-ts-errors"
|
||||
"connor4312.esbuild-problem-matchers"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
# PostHog Analytics Configuration
|
||||
# Replace these values with your actual PostHog API key and host
|
||||
NEXT_PUBLIC_POSTHOG_KEY=your_posthog_api_key_here
|
||||
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||
|
||||
# Basin Form Endpoint for Static Form Submissions
|
||||
# Replace this with your actual Basin form endpoint (e.g., https://usebasin.com/f/your-form-id)
|
||||
NEXT_PUBLIC_BASIN_ENDPOINT=https://usebasin.com/f/your-form-id-here
|
||||
|
||||
TURSO_CONNECTION_URL=libsql://development-roo-code.aws-us-east-1.turso.io
|
||||
TURSO_AUTH_TOKEN=your-auth-token-here
|
||||
42
apps/website/.gitignore
vendored
|
|
@ -1,42 +0,0 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
!.env.example
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/app/globals.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import { defineConfig } from "drizzle-kit"
|
||||
|
||||
const dialect = process.env.BENCHMARKS_DB_PATH ? "sqlite" : "turso"
|
||||
|
||||
const dbCredentials = process.env.BENCHMARKS_DB_PATH
|
||||
? { url: process.env.BENCHMARKS_DB_PATH }
|
||||
: { url: process.env.TURSO_CONNECTION_URL!, authToken: process.env.TURSO_AUTH_TOKEN! }
|
||||
|
||||
export default defineConfig({
|
||||
out: "./drizzle",
|
||||
schema: "./src/db/schema.ts",
|
||||
dialect,
|
||||
dbCredentials,
|
||||
})
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import { nextJsConfig } from "@roo-code/config-eslint/next-js"
|
||||
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
export default [...nextJsConfig]
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import type { NextConfig } from "next"
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
webpack: (config) => {
|
||||
config.resolve.extensionAlias = { ".js": [".ts", ".tsx", ".js", ".jsx"] }
|
||||
return config
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
// Redirect www to non-www
|
||||
{
|
||||
source: "/:path*",
|
||||
has: [{ type: "host", value: "www.roocode.com" }],
|
||||
destination: "https://roocode.com/:path*",
|
||||
permanent: true,
|
||||
},
|
||||
// Redirect HTTP to HTTPS
|
||||
{
|
||||
source: "/:path*",
|
||||
has: [{ type: "header", key: "x-forwarded-proto", value: "http" }],
|
||||
destination: "https://roocode.com/:path*",
|
||||
permanent: true,
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
{
|
||||
"name": "@roo-code/website",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"lint": "next lint",
|
||||
"check-types": "tsc --noEmit",
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"drizzle-kit": "dotenvx run -f .env -- tsx node_modules/drizzle-kit/bin.cjs",
|
||||
"db:generate": "pnpm drizzle-kit generate",
|
||||
"db:migrate": "pnpm drizzle-kit migrate",
|
||||
"db:push": "pnpm drizzle-kit push",
|
||||
"db:pull": "pnpm drizzle-kit pull",
|
||||
"db:check": "pnpm drizzle-kit check",
|
||||
"db:up": "pnpm drizzle-kit up",
|
||||
"db:studio": "pnpm drizzle-kit studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.15.7",
|
||||
"@radix-ui/react-dialog": "^1.1.14",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@roo-code/types": "workspace:^",
|
||||
"@tanstack/react-query": "^5.79.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"drizzle-orm": "^0.41.0",
|
||||
"drizzle-zod": "^0.7.1",
|
||||
"embla-carousel-auto-scroll": "^8.6.0",
|
||||
"embla-carousel-autoplay": "^8.6.0",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"framer-motion": "^12.15.0",
|
||||
"lucide-react": "^0.479.0",
|
||||
"next": "15.2.4",
|
||||
"next-themes": "^0.4.6",
|
||||
"posthog-js": "^1.248.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"recharts": "^2.15.3",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "^3.25.41"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@roo-code/config-eslint": "workspace:^",
|
||||
"@roo-code/config-typescript": "workspace:^",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/node": "^20.17.54",
|
||||
"@types/react": "^18.3.23",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"drizzle-kit": "^0.30.6",
|
||||
"postcss": "^8.5.4",
|
||||
"tailwindcss": "^3.4.17"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
Before Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 322 B |
|
Before Width: | Height: | Size: 600 B |
|
Before Width: | Height: | Size: 15 KiB |
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
|
||||
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
|
@ -1,427 +0,0 @@
|
|||
import { Code, CheckCircle, Shield, Users, Zap, Workflow } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui"
|
||||
import { AnimatedText } from "@/components/animated-text"
|
||||
import { AnimatedBackground } from "@/components/homepage"
|
||||
import { ContactForm } from "@/components/enterprise/contact-form"
|
||||
|
||||
export default async function Enterprise() {
|
||||
return (
|
||||
<>
|
||||
{/* Hero Section */}
|
||||
<section className="relative flex h-[calc(100vh-theme(spacing.16))] items-center overflow-hidden">
|
||||
<AnimatedBackground />
|
||||
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid gap-8 md:gap-12 lg:grid-cols-2 lg:gap-16">
|
||||
<div className="flex flex-col justify-center space-y-6 sm:space-y-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl lg:text-6xl">
|
||||
<span className="block">Roo Code for</span>
|
||||
<AnimatedText className="bg-gradient-to-r from-blue-400 to-cyan-400 bg-clip-text text-transparent">
|
||||
Enterprise
|
||||
</AnimatedText>
|
||||
</h1>
|
||||
<p className="mt-4 max-w-md text-base text-muted-foreground sm:mt-6 sm:text-lg">
|
||||
A next-generation, AI-powered{" "}
|
||||
<AnimatedText className="bg-gradient-to-r from-blue-400 to-cyan-400 bg-clip-text text-transparent">
|
||||
coding partner
|
||||
</AnimatedText>{" "}
|
||||
for enterprise development teams.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-3 sm:flex-row sm:space-x-4 sm:space-y-0">
|
||||
<Button
|
||||
size="lg"
|
||||
className="w-full hover:bg-gray-200 dark:bg-white dark:text-black sm:w-auto"
|
||||
asChild>
|
||||
<a href="#contact" className="flex w-full items-center justify-center">
|
||||
Request a Demo
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="ml-2 h-4 w-4"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L12.586 11H5a1 1 0 110-2h7.586l-2.293-2.293a1 1 0 010-1.414z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</Button>
|
||||
<Button variant="outline" size="lg" className="w-full sm:w-auto">
|
||||
<a href="#benefits" className="flex w-full items-center justify-center">
|
||||
Learn More
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-8 flex items-center justify-center lg:mt-0">
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="h-[250px] w-[250px] rounded-full bg-blue-500/20 blur-[100px] sm:h-[300px] sm:w-[300px] md:h-[350px] md:w-[350px]" />
|
||||
</div>
|
||||
<div className="relative z-10 rounded-lg border border-border bg-card p-6 shadow-lg">
|
||||
<div className="mb-4 flex items-center space-x-2">
|
||||
<Code className="h-6 w-6 text-blue-400" />
|
||||
<h3 className="text-lg font-semibold">Roo Code Enterprise</h3>
|
||||
</div>
|
||||
<p className="mb-4 text-sm text-muted-foreground">
|
||||
An AI extension of your team that handles coding tasks, from new code generation to
|
||||
refactoring, bug fixing, and documentation.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-400" />
|
||||
<span className="text-sm">Accelerate development cycles</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-400" />
|
||||
<span className="text-sm">Enterprise-grade security</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-400" />
|
||||
<span className="text-sm">Custom-tailored to your workflow</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-400" />
|
||||
<span className="text-sm">Improve collaboration and onboarding</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Key Messaging Sections */}
|
||||
<section id="benefits" className="bg-secondary/50 py-16">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-12 text-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl">Empower Your Development Team</h2>
|
||||
<p className="mx-auto mt-4 max-w-2xl text-lg text-muted-foreground">
|
||||
Roo Code functions like an entire AI dev team embedded in your developers' IDE, ready
|
||||
to accelerate software delivery and improve code quality.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
{/* Card 1 */}
|
||||
<div className="rounded-lg border border-border bg-card p-6 shadow-sm transition-all hover:shadow-md">
|
||||
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900/20">
|
||||
<Zap className="h-6 w-6 text-blue-500" />
|
||||
</div>
|
||||
<h3 className="mb-2 text-xl font-bold">Accelerate Development Cycles</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Supercharge development with AI assistance that helps developers code faster while
|
||||
maintaining quality.
|
||||
</p>
|
||||
<ul className="mt-4 space-y-2">
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>Faster time-to-market</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>AI pair-programming</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>Improved code quality</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Card 2 */}
|
||||
<div className="rounded-lg border border-border bg-card p-6 shadow-sm transition-all hover:shadow-md">
|
||||
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900/20">
|
||||
<Users className="h-6 w-6 text-blue-500" />
|
||||
</div>
|
||||
<h3 className="mb-2 text-xl font-bold">Augment Your Team with AI Agents</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Roo Code functions like an AI extension of your team, handling various coding tasks.
|
||||
</p>
|
||||
<ul className="mt-4 space-y-2">
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>New code generation</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>Refactoring and bug fixing</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>Automate complex migrations</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Card 3 */}
|
||||
<div className="rounded-lg border border-border bg-card p-6 shadow-sm transition-all hover:shadow-md">
|
||||
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900/20">
|
||||
<Shield className="h-6 w-6 text-blue-500" />
|
||||
</div>
|
||||
<h3 className="mb-2 text-xl font-bold">Enterprise-Grade Security</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Keep your data private with on-premises models, keeping proprietary code in-house.
|
||||
</p>
|
||||
<ul className="mt-4 space-y-2">
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>Security and compliance</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>No external cloud dependencies</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>Open-source and extensible</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Card 4 */}
|
||||
<div className="rounded-lg border border-border bg-card p-6 shadow-sm transition-all hover:shadow-md">
|
||||
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900/20">
|
||||
<Workflow className="h-6 w-6 text-blue-500" />
|
||||
</div>
|
||||
<h3 className="mb-2 text-xl font-bold">Custom-Tailored to Your Workflow</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Developers can create Custom Modes for specialized tasks like security auditing or
|
||||
performance tuning.
|
||||
</p>
|
||||
<ul className="mt-4 space-y-2">
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>Integrate with internal tools</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>Adapt to existing workflows</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>Custom AI behaviors</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Card 5 */}
|
||||
<div className="rounded-lg border border-border bg-card p-6 shadow-sm transition-all hover:shadow-md">
|
||||
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900/20">
|
||||
<Users className="h-6 w-6 text-blue-500" />
|
||||
</div>
|
||||
<h3 className="mb-2 text-xl font-bold">Collaboration and Onboarding</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Ask Mode enables developers to query their codebase in plain language and receive
|
||||
instant answers.
|
||||
</p>
|
||||
<ul className="mt-4 space-y-2">
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>Accelerates onboarding</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>Improves cross-team collaboration</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>Makes code more accessible</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Card 6 */}
|
||||
<div className="rounded-lg border border-border bg-card p-6 shadow-sm transition-all hover:shadow-md">
|
||||
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900/20">
|
||||
<Zap className="h-6 w-6 text-blue-500" />
|
||||
</div>
|
||||
<h3 className="mb-2 text-xl font-bold">Faster Delivery, Lower Costs</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Automate routine tasks to accelerate software releases and reduce costs.
|
||||
</p>
|
||||
<ul className="mt-4 space-y-2">
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>Improved code quality & consistency</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>Empowered developers, happier teams</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 shrink-0 text-green-500" />
|
||||
<span>Rapid knowledge sharing</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Differentiator Section */}
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-12 text-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl">What Makes Roo Code Unique</h2>
|
||||
<p className="mx-auto mt-4 max-w-2xl text-lg text-muted-foreground">
|
||||
Unlike traditional code editors or basic autocomplete tools, Roo Code is an autonomous
|
||||
coding agent with powerful capabilities.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-8 md:grid-cols-2">
|
||||
<div className="rounded-lg border border-border bg-card p-8 shadow-sm">
|
||||
<h3 className="mb-4 text-2xl font-bold">Traditional AI Coding Assistants</h3>
|
||||
<ul className="space-y-3">
|
||||
<li className="flex items-start">
|
||||
<svg
|
||||
className="mr-2 mt-0.5 h-5 w-5 text-red-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<span>Limited to autocomplete and simple suggestions</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<svg
|
||||
className="mr-2 mt-0.5 h-5 w-5 text-red-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<span>Lack project-wide context understanding</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<svg
|
||||
className="mr-2 mt-0.5 h-5 w-5 text-red-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<span>Can't execute commands or perform web actions</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<svg
|
||||
className="mr-2 mt-0.5 h-5 w-5 text-red-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<span>No customization for enterprise workflows</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<svg
|
||||
className="mr-2 mt-0.5 h-5 w-5 text-red-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<span>Often require sending code to external cloud services</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-border bg-card p-8 shadow-sm">
|
||||
<h3 className="mb-4 text-2xl font-bold text-blue-400">Roo Code Enterprise</h3>
|
||||
<ul className="space-y-3">
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 text-green-500" />
|
||||
<span>Full-featured AI dev team with natural language communication</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 text-green-500" />
|
||||
<span>Deep understanding of your entire codebase</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 text-green-500" />
|
||||
<span>Can run tests, execute commands, and perform web actions</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 text-green-500" />
|
||||
<span>Custom modes for specialized enterprise tasks</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<CheckCircle className="mr-2 mt-0.5 h-5 w-5 text-green-500" />
|
||||
<span>On-premises deployment option for data privacy</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section id="contact" className="bg-secondary/50 py-16">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-3xl text-center">
|
||||
<h2 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl">
|
||||
Ready to Transform Your Development Process?
|
||||
</h2>
|
||||
<p className="mb-8 text-lg text-muted-foreground">
|
||||
Join our early access program and be among the first to experience the power of Roo Code for
|
||||
Enterprise.
|
||||
</p>
|
||||
<div className="grid gap-4 sm:grid-cols-2 sm:gap-6">
|
||||
<div className="rounded-lg border border-border bg-card p-6 text-center shadow-sm">
|
||||
<h3 className="mb-2 text-xl font-bold">Become an Early Access Partner</h3>
|
||||
<p className="mb-4 text-muted-foreground">
|
||||
Collaborate in shaping Roo Code's enterprise solution.
|
||||
</p>
|
||||
<ContactForm formType="early-access" buttonText="Apply Now" buttonClassName="w-full" />
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-card p-6 text-center shadow-sm">
|
||||
<h3 className="mb-2 text-xl font-bold">Request a Demo</h3>
|
||||
<p className="mb-4 text-muted-foreground">
|
||||
See Roo Code's enterprise capabilities in action.
|
||||
</p>
|
||||
<ContactForm formType="demo" buttonText="Contact Us" buttonClassName="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8">
|
||||
<Button variant="outline" size="lg">
|
||||
<a
|
||||
href="mailto:enterprise@roocode.com?subject=Enterprise Guide Request"
|
||||
className="flex items-center justify-center">
|
||||
Download the Enterprise Guide
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useMemo } from "react"
|
||||
import { ScatterChart, Scatter, XAxis, YAxis, Label, Customized, Cross } from "recharts"
|
||||
|
||||
import { TaskMetrics, type Run } from "@/db"
|
||||
|
||||
import { ChartConfig, ChartLegend, ChartLegendContent } from "@/components/ui/chart"
|
||||
import { formatTokens, formatCurrency, formatDuration, formatScore } from "@/lib"
|
||||
import {
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCaption,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui"
|
||||
import { useOpenRouterModels } from "@/lib/hooks/use-open-router-models"
|
||||
|
||||
export function Evals({
|
||||
runs,
|
||||
}: {
|
||||
runs: (Run & {
|
||||
label: string
|
||||
score: number
|
||||
languageScores?: Record<"go" | "java" | "javascript" | "python" | "rust", number>
|
||||
taskMetrics: TaskMetrics
|
||||
modelId?: string
|
||||
})[]
|
||||
}) {
|
||||
const { data: openRouterModels } = useOpenRouterModels()
|
||||
|
||||
const tableData = useMemo(
|
||||
() =>
|
||||
runs.map((run) => ({
|
||||
...run,
|
||||
label: run.description || run.model,
|
||||
score: run.score,
|
||||
cost: run.taskMetrics.cost,
|
||||
model: openRouterModels?.[run.modelId ?? ""],
|
||||
modelInfo: openRouterModels?.[run.modelId ?? ""]?.modelInfo,
|
||||
})),
|
||||
[runs, openRouterModels],
|
||||
)
|
||||
|
||||
const chartData = useMemo(() => tableData.filter(({ cost }) => cost < 100), [tableData])
|
||||
|
||||
const chartConfig = useMemo(
|
||||
() => chartData.reduce((acc, run) => ({ ...acc, [run.label]: run }), {} as ChartConfig),
|
||||
[chartData],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex max-w-screen-lg flex-col gap-8 p-8">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
Roo Code tests each frontier model against{" "}
|
||||
<a href="https://github.com/cte/evals/" className="underline">
|
||||
a suite of hundreds of exercises
|
||||
</a>{" "}
|
||||
across 5 programming languages with varying difficulty. These results can help you find the right
|
||||
price-to-intelligence ratio for your use case.
|
||||
</div>
|
||||
<div>
|
||||
Want to see the results for a model we haven't tested yet? Ping us in{" "}
|
||||
<a href="https://discord.gg/roocode" className="underline">
|
||||
Discord
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
<Table className="border">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead colSpan={2} className="border-r text-center">
|
||||
Model
|
||||
</TableHead>
|
||||
<TableHead colSpan={3} className="border-r text-center">
|
||||
Metrics
|
||||
</TableHead>
|
||||
<TableHead colSpan={6} className="text-center">
|
||||
Scores
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>
|
||||
Name
|
||||
<div className="text-xs opacity-50">Context Window</div>
|
||||
</TableHead>
|
||||
<TableHead className="border-r">
|
||||
Price
|
||||
<div className="text-xs opacity-50">In / Out</div>
|
||||
</TableHead>
|
||||
<TableHead>Duration</TableHead>
|
||||
<TableHead>
|
||||
Tokens
|
||||
<div className="text-xs opacity-50">In / Out</div>
|
||||
</TableHead>
|
||||
<TableHead className="border-r">
|
||||
Cost
|
||||
<div className="text-xs opacity-50">USD</div>
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<i className="devicon-go-plain text-lg" title="Go" />
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<i className="devicon-java-plain text-lg" title="Java" />
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<i className="devicon-javascript-plain text-lg" title="JavaScript" />
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<i className="devicon-python-plain text-lg" title="Python" />
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<i className="devicon-rust-original text-lg" title="Rust" />
|
||||
</TableHead>
|
||||
<TableHead>Total</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody className="font-mono">
|
||||
{tableData.map((run) => (
|
||||
<TableRow key={run.id}>
|
||||
<TableCell title={run.model?.description}>
|
||||
<div className="font-sans">{run.model?.name || run.label}</div>
|
||||
<div className="text-xs opacity-50">
|
||||
{formatTokens(run.modelInfo?.contextWindow ?? 0)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="border-r">
|
||||
<div className="flex flex-row gap-2">
|
||||
<div>{formatCurrency(run.modelInfo?.inputPrice ?? 0)}</div>
|
||||
<div className="opacity-25">/</div>
|
||||
<div>{formatCurrency(run.modelInfo?.outputPrice ?? 0)}</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="font-mono">{formatDuration(run.taskMetrics.duration)}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-row gap-2">
|
||||
<div>{formatTokens(run.taskMetrics.tokensIn)}</div>
|
||||
<div className="opacity-25">/</div>
|
||||
<div>{formatTokens(run.taskMetrics.tokensOut)}</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="border-r">{formatCurrency(run.taskMetrics.cost)}</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
{formatScore(run.languageScores?.go ?? 0)}%
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
{formatScore(run.languageScores?.java ?? 0)}%
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
{formatScore(run.languageScores?.javascript ?? 0)}%
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
{formatScore(run.languageScores?.python ?? 0)}%
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
{formatScore(run.languageScores?.rust ?? 0)}%
|
||||
</TableCell>
|
||||
<TableCell className="font-bold">{run.score}%</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
<TableCaption>
|
||||
<div className="pb-4 font-medium">Cost Versus Score</div>
|
||||
<ChartContainer config={chartConfig} className="h-[500px] w-full">
|
||||
<ScatterChart margin={{ top: 0, right: 0, bottom: 0, left: 20 }}>
|
||||
<XAxis
|
||||
type="number"
|
||||
dataKey="cost"
|
||||
name="Cost"
|
||||
domain={[
|
||||
(dataMin: number) => Math.round((dataMin - 5) / 5) * 5,
|
||||
(dataMax: number) => Math.round((dataMax + 5) / 5) * 5,
|
||||
]}
|
||||
tickFormatter={(value) => formatCurrency(value)}>
|
||||
<Label value="Cost" position="bottom" offset={0} />
|
||||
</XAxis>
|
||||
<YAxis
|
||||
type="number"
|
||||
dataKey="score"
|
||||
name="Score"
|
||||
domain={[
|
||||
(dataMin: number) => Math.max(0, Math.round((dataMin - 5) / 5) * 5),
|
||||
(dataMax: number) => Math.min(100, Math.round((dataMax + 5) / 5) * 5),
|
||||
]}
|
||||
tickFormatter={(value) => `${value}%`}>
|
||||
<Label value="Score" angle={-90} position="left" dy={-15} />
|
||||
</YAxis>
|
||||
<ChartTooltip content={<ChartTooltipContent labelKey="label" hideIndicator />} />
|
||||
<Customized component={renderQuadrant} />
|
||||
{chartData.map((d, i) => (
|
||||
<Scatter key={d.label} name={d.label} data={[d]} fill={`hsl(var(--chart-${i + 1}))`} />
|
||||
))}
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
</ScatterChart>
|
||||
</ChartContainer>
|
||||
<div className="py-4 text-xs opacity-50">
|
||||
(Note: Very expensive models are exluded from the scatter plot.)
|
||||
</div>
|
||||
</TableCaption>
|
||||
</Table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const renderQuadrant = (props: any) => (
|
||||
<Cross
|
||||
width={props.width}
|
||||
height={props.height}
|
||||
x={props.width / 2 + 35}
|
||||
y={props.height / 2 - 15}
|
||||
top={0}
|
||||
left={0}
|
||||
stroke="currentColor"
|
||||
opacity={0.1}
|
||||
/>
|
||||
)
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import type { Metadata } from "next"
|
||||
|
||||
import { rooCodeSettingsSchema, getModelId } from "@roo-code/types"
|
||||
|
||||
import { getRuns } from "@/db"
|
||||
import { getLanguageScores } from "@/lib/server"
|
||||
import { formatScore } from "@/lib"
|
||||
|
||||
import { Evals } from "./evals"
|
||||
|
||||
export const revalidate = 300
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Roo Code Evals",
|
||||
openGraph: {
|
||||
title: "Roo Code Evals",
|
||||
description: "Quantitative evals of LLM coding skills.",
|
||||
url: "https://roocode.com/evals",
|
||||
siteName: "Roo Code",
|
||||
images: {
|
||||
url: "https://i.imgur.com/ijP7aZm.png",
|
||||
width: 1954,
|
||||
height: 1088,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const languageScores = await getLanguageScores()
|
||||
|
||||
const runs = (await getRuns())
|
||||
.filter((run) => !!run.taskMetrics)
|
||||
.filter(({ settings }) => rooCodeSettingsSchema.safeParse(settings).success)
|
||||
.sort((a, b) => b.passed - a.passed)
|
||||
.map((run) => {
|
||||
const settings = rooCodeSettingsSchema.parse(run.settings)
|
||||
|
||||
return {
|
||||
...run,
|
||||
label: run.description || run.model,
|
||||
score: formatScore(run.passed / (run.passed + run.failed)),
|
||||
languageScores: languageScores[run.id],
|
||||
taskMetrics: run.taskMetrics!,
|
||||
modelId: getModelId(settings),
|
||||
}
|
||||
})
|
||||
|
||||
return <Evals runs={runs} />
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 0% 3.9%;
|
||||
--radius: 0.5rem;
|
||||
|
||||
--chart-1: 0 100% 50%;
|
||||
--chart-2: 29 100% 50%;
|
||||
--chart-3: 51 100% 50%;
|
||||
--chart-4: 83 100% 50%;
|
||||
--chart-5: 115 100% 50%;
|
||||
--chart-6: 147 100% 50%;
|
||||
--chart-7: 168 100% 50%;
|
||||
--chart-8: 196 100% 50%;
|
||||
--chart-9: 224 100% 50%;
|
||||
--chart-10: 261 100% 50%;
|
||||
--chart-11: 279 100% 50%;
|
||||
--chart-12: 298 100% 50%;
|
||||
--chart-13: 324 100% 50%;
|
||||
--chart-14: 358 100% 50%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 0 0% 0%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 0 0% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 0 0% 83.1%;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
import React from "react"
|
||||
import type { Metadata } from "next"
|
||||
import { Inter } from "next/font/google"
|
||||
|
||||
import { Providers } from "@/components/providers"
|
||||
|
||||
import Shell from "./shell"
|
||||
|
||||
import "./globals.css"
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Roo Code – Your AI-Powered Dev Team in VS Code",
|
||||
description:
|
||||
"Roo Code puts an entire AI dev team right in your editor, outpacing closed tools with deep project-wide context, multi-step agentic coding, and unmatched developer-centric flexibility.",
|
||||
alternates: {
|
||||
canonical: "https://roocode.com",
|
||||
},
|
||||
icons: {
|
||||
icon: [
|
||||
{ url: "/favicon.ico" },
|
||||
{ url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" },
|
||||
{ url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" },
|
||||
],
|
||||
apple: [{ url: "/apple-touch-icon.png" }],
|
||||
other: [
|
||||
{
|
||||
rel: "android-chrome-192x192",
|
||||
url: "/android-chrome-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
rel: "android-chrome-512x512",
|
||||
url: "/android-chrome-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/devicon.min.css"
|
||||
/>
|
||||
</head>
|
||||
<body className={inter.className}>
|
||||
<div itemScope itemType="https://schema.org/WebSite">
|
||||
<link itemProp="url" href="https://roocode.com" />
|
||||
<meta itemProp="name" content="Roo Code" />
|
||||
</div>
|
||||
<Providers>
|
||||
<Shell>{children}</Shell>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
/* eslint-disable react/jsx-no-target-blank */
|
||||
|
||||
import { getVSCodeDownloads } from "@/lib/stats"
|
||||
|
||||
import { Button } from "@/components/ui"
|
||||
import { AnimatedText } from "@/components/animated-text"
|
||||
import {
|
||||
AnimatedBackground,
|
||||
InstallSection,
|
||||
Features,
|
||||
Testimonials,
|
||||
FAQSection,
|
||||
CodeExample,
|
||||
} from "@/components/homepage"
|
||||
|
||||
// Invalidate cache when a request comes in, at most once every hour.
|
||||
export const revalidate = 3600
|
||||
|
||||
export default async function Home() {
|
||||
const downloads = await getVSCodeDownloads()
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="relative flex h-[calc(125vh-theme(spacing.16))] items-center overflow-hidden md:h-[calc(100svh-theme(spacing.16))] lg:h-[calc(100vh-theme(spacing.16))]">
|
||||
<AnimatedBackground />
|
||||
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid gap-8 md:gap-12 lg:grid-cols-2 lg:gap-16">
|
||||
<div className="flex flex-col justify-center space-y-6 sm:space-y-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl lg:text-6xl">
|
||||
<span className="block">Your</span>
|
||||
<AnimatedText className="bg-gradient-to-r from-blue-400 to-cyan-400 bg-clip-text text-transparent">
|
||||
AI-Powered
|
||||
</AnimatedText>
|
||||
<span className="block">Dev Team, Right in Your Editor.</span>
|
||||
</h1>
|
||||
<p className="mt-4 max-w-md text-base text-muted-foreground sm:mt-6 sm:text-lg">
|
||||
Supercharge your editor with AI that{" "}
|
||||
<AnimatedText className="bg-gradient-to-r from-blue-400 to-cyan-400 bg-clip-text text-transparent">
|
||||
understands your codebase
|
||||
</AnimatedText>
|
||||
, streamlines development, and helps you write, refactor, and debug with ease.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-3 sm:flex-row sm:space-x-4 sm:space-y-0">
|
||||
<Button
|
||||
size="lg"
|
||||
className="w-full hover:bg-gray-200 dark:bg-white dark:text-black sm:w-auto">
|
||||
<a
|
||||
href="https://marketplace.visualstudio.com/items?itemName=RooVeterinaryInc.roo-cline"
|
||||
target="_blank"
|
||||
className="flex w-full items-center justify-center">
|
||||
Install Roo Code
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="ml-2 h-4 w-4"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L12.586 11H5a1 1 0 110-2h7.586l-2.293-2.293a1 1 0 010-1.414z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</Button>
|
||||
<Button variant="outline" size="lg" className="w-full sm:w-auto">
|
||||
<a
|
||||
href="https://docs.roocode.com"
|
||||
target="_blank"
|
||||
className="flex w-full items-center justify-center">
|
||||
View Documentation
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-8 flex items-center justify-center lg:mt-0">
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="h-[250px] w-[250px] rounded-full bg-blue-500/20 blur-[100px] sm:h-[300px] sm:w-[300px] md:h-[350px] md:w-[350px]" />
|
||||
</div>
|
||||
<CodeExample />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div id="features">
|
||||
<Features />
|
||||
</div>
|
||||
<div id="testimonials">
|
||||
<Testimonials />
|
||||
</div>
|
||||
<div id="faq">
|
||||
<FAQSection />
|
||||
</div>
|
||||
<InstallSection downloads={downloads} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
import { Metadata } from "next"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Privacy Policy - Roo Code Marketing Website",
|
||||
description:
|
||||
"Privacy policy for the Roo Code marketing website. Learn how we handle your data and protect your privacy.",
|
||||
}
|
||||
|
||||
export default function Privacy() {
|
||||
return (
|
||||
<>
|
||||
<div className="container mx-auto px-4 py-12 sm:px-6 lg:px-8">
|
||||
<div className="prose prose-lg mx-auto max-w-3xl dark:prose-invert">
|
||||
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl">
|
||||
Roo Code Marketing Landing Page Privacy Policy
|
||||
</h1>
|
||||
<p className="text-muted-foreground">Last Updated: March 7th, 2025</p>
|
||||
|
||||
<p className="lead">
|
||||
Roo Code respects your privacy and is committed to being transparent about how data is collected
|
||||
and used on our marketing landing page. This policy focuses on data handling for the Roo Code
|
||||
marketing website. For details on how your data is handled within the Roo Code extension itself,
|
||||
please refer to our separate{" "}
|
||||
<a
|
||||
href="https://github.com/RooVetGit/Roo-Code/blob/main/PRIVACY.md"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline">
|
||||
Roo Code Extension Privacy Policy
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
|
||||
<h2 className="mt-8 text-2xl font-bold">Where Your Data Goes (And Where It Doesn't)</h2>
|
||||
|
||||
<h3 className="mt-6 text-xl font-bold">Website Analytics & Tracking</h3>
|
||||
<ul>
|
||||
<li>
|
||||
We use PostHog (and its standard features) on our marketing landing page to analyze site
|
||||
traffic and usage trends. This collection includes information such as your IP address,
|
||||
browser type, device information, and pages visited.
|
||||
</li>
|
||||
<li>
|
||||
These analytics help us understand how users engage with the website, so we can improve
|
||||
content and design.
|
||||
</li>
|
||||
<li>We do not collect code, project data, or any AI-related prompts on this page.</li>
|
||||
</ul>
|
||||
|
||||
<h3 className="mt-6 text-xl font-bold">Cookies and Similar Technologies</h3>
|
||||
<ul>
|
||||
<li>
|
||||
Our marketing website may use cookies or similar tracking technologies to remember user
|
||||
preferences and provide aggregated analytics.
|
||||
</li>
|
||||
<li>
|
||||
Cookies help with things like user session management, remembering certain selections or
|
||||
preferences, and compiling anonymous statistics.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 className="mt-6 text-xl font-bold">Forms & Voluntary Submissions</h3>
|
||||
<ul>
|
||||
<li>
|
||||
If you submit your email or other personal data on our landing page (for example, to receive
|
||||
updates or join a waiting list), we collect that information voluntarily provided by you.
|
||||
</li>
|
||||
<li>
|
||||
We do not share or sell this data to third parties for their own marketing purposes. It is
|
||||
used only to communicate with you about Roo Code, respond to inquiries, or send updates
|
||||
you've requested.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 className="mt-6 text-xl font-bold">Third-Party Integrations</h3>
|
||||
<ul>
|
||||
<li>
|
||||
Our website may embed content or links to external platforms (e.g., for processing payments
|
||||
or handling support). Any data you provide through these external sites is governed by the
|
||||
privacy policies of those platforms.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2 className="mt-8 text-2xl font-bold">How We Use Your Data</h2>
|
||||
|
||||
<h3 className="mt-6 text-xl font-bold">Site Improvements & Marketing</h3>
|
||||
<ul>
|
||||
<li>
|
||||
We analyze aggregated user behavior to measure the effectiveness of our site, troubleshoot
|
||||
any issues, and guide future improvements.
|
||||
</li>
|
||||
<li>
|
||||
If you sign up for newsletters or updates, we use your email or other contact information
|
||||
only to send you relevant Roo Code communications.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 className="mt-6 text-xl font-bold">No Selling or Sharing of Data</h3>
|
||||
<ul>
|
||||
<li>
|
||||
We do not sell or share your personally identifiable information with third parties for
|
||||
their marketing.
|
||||
</li>
|
||||
<li>We do not train any models on your marketing site data.</li>
|
||||
</ul>
|
||||
|
||||
<h2 className="mt-8 text-2xl font-bold">Your Choices & Control</h2>
|
||||
|
||||
<h3 className="mt-6 text-xl font-bold">Manage Cookies</h3>
|
||||
<ul>
|
||||
<li>
|
||||
Most browsers allow you to manage or block cookies. If you disable cookies, some features of
|
||||
the site may not function properly.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 className="mt-6 text-xl font-bold">Opt-Out of Communications</h3>
|
||||
<ul>
|
||||
<li>
|
||||
If you have signed up to receive updates, you can unsubscribe anytime by following the
|
||||
instructions in our emails or contacting us directly.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 className="mt-6 text-xl font-bold">Request Deletion</h3>
|
||||
<ul>
|
||||
<li>
|
||||
You may request the deletion of any personal data you've provided through our marketing
|
||||
forms by reaching out to us at{" "}
|
||||
<a href="mailto:support@roocode.com" className="text-primary hover:underline">
|
||||
support@roocode.com
|
||||
</a>
|
||||
.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2 className="mt-8 text-2xl font-bold">Security & Updates</h2>
|
||||
<ul>
|
||||
<li>
|
||||
We take reasonable measures to protect your data from unauthorized access or disclosure, but
|
||||
no website can be 100% secure.
|
||||
</li>
|
||||
<li>
|
||||
If our privacy practices for the marketing site change, we will update this policy and note
|
||||
the effective date at the top.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2 className="mt-8 text-2xl font-bold">Contact Us</h2>
|
||||
<p>
|
||||
If you have questions or concerns about this Privacy Policy or wish to make a request regarding
|
||||
your data, please reach out to us at{" "}
|
||||
<a href="mailto:support@roocode.com" className="text-primary hover:underline">
|
||||
support@roocode.com
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
|
||||
<div className="mt-8 border-t border-border pt-6">
|
||||
<p className="text-muted-foreground">
|
||||
By using the Roo Code marketing landing page, you agree to this Privacy Policy. If you use
|
||||
the Roo Code extension, please see our separate{" "}
|
||||
<a
|
||||
href="https://github.com/RooVetGit/Roo-Code/blob/main/PRIVACY.md"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline">
|
||||
Roo Code Extension Privacy Policy
|
||||
</a>{" "}
|
||||
for details on data handling in the extension.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import { getGitHubStars, getVSCodeDownloads } from "@/lib/stats"
|
||||
|
||||
import { NavBar, Footer } from "@/components/chromes"
|
||||
|
||||
// Invalidate cache when a request comes in, at most once every hour.
|
||||
export const revalidate = 3600
|
||||
|
||||
export default async function Shell({ children }: { children: React.ReactNode }) {
|
||||
const [stars, downloads] = await Promise.all([getGitHubStars(), getVSCodeDownloads()])
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col bg-background text-foreground">
|
||||
<NavBar stars={stars} downloads={downloads} />
|
||||
<main className="flex-1">{children}</main>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://roocode.com/</loc>
|
||||
<lastmod>2025-03-13T22:26:09Z</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
import { motion } from "framer-motion"
|
||||
|
||||
interface AnimatedTextProps {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function AnimatedText({ children, className }: AnimatedTextProps) {
|
||||
return (
|
||||
<motion.span
|
||||
className={className}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{
|
||||
duration: 0.8,
|
||||
ease: [0.2, 0.65, 0.3, 0.9],
|
||||
}}>
|
||||
{children}
|
||||
</motion.span>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,269 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useState, useRef, useEffect } from "react"
|
||||
import Link from "next/link"
|
||||
import Image from "next/image"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
import { RxGithubLogo, RxDiscordLogo } from "react-icons/rx"
|
||||
import { FaReddit } from "react-icons/fa6"
|
||||
|
||||
import { EXTERNAL_LINKS, INTERNAL_LINKS } from "@/lib/constants"
|
||||
import { useLogoSrc } from "@/lib/hooks/use-logo-src"
|
||||
import { ScrollButton } from "@/components/ui"
|
||||
|
||||
export function Footer() {
|
||||
const [privacyDropdownOpen, setPrivacyDropdownOpen] = useState(false)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
const logoSrc = useLogoSrc()
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setPrivacyDropdownOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside)
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside)
|
||||
}
|
||||
}, [])
|
||||
return (
|
||||
<footer className="border-t border-border bg-background">
|
||||
<div className="mx-auto max-w-7xl px-6 pb-6 pt-12 md:pb-8 md:pt-16 lg:px-8">
|
||||
<div className="xl:grid xl:grid-cols-3 xl:gap-8">
|
||||
<div className="space-y-8">
|
||||
<div className="flex items-center">
|
||||
<Image src={logoSrc} alt="Roo Code Logo" width={120} height={40} className="h-6 w-auto" />
|
||||
</div>
|
||||
<p className="max-w-md text-sm leading-6 text-muted-foreground md:pr-16 lg:pr-32">
|
||||
Empowering developers to build better software faster with AI-powered tools and insights.
|
||||
</p>
|
||||
<div className="flex space-x-5">
|
||||
<a
|
||||
href={EXTERNAL_LINKS.GITHUB}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground transition-colors hover:text-foreground">
|
||||
<RxGithubLogo className="h-6 w-6" />
|
||||
<span className="sr-only">GitHub</span>
|
||||
</a>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DISCORD}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground transition-colors hover:text-foreground">
|
||||
<RxDiscordLogo className="h-6 w-6" />
|
||||
<span className="sr-only">Discord</span>
|
||||
</a>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.REDDIT}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground transition-colors hover:text-foreground">
|
||||
<FaReddit className="h-6 w-6" />
|
||||
<span className="sr-only">Reddit</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-16 grid grid-cols-2 gap-8 xl:col-span-2 xl:mt-0">
|
||||
<div className="md:grid md:grid-cols-2 md:gap-8">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold uppercase leading-6 text-foreground">Product</h3>
|
||||
<ul className="mt-6 space-y-4">
|
||||
<li>
|
||||
<ScrollButton
|
||||
targetId="features"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Features
|
||||
</ScrollButton>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/enterprise"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Enterprise
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<ScrollButton
|
||||
targetId="testimonials"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Testimonials
|
||||
</ScrollButton>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.INTEGRATIONS}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Integrations
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.CHANGELOG}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Changelog
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-10 md:mt-0">
|
||||
<h3 className="text-sm font-semibold uppercase leading-6 text-foreground">Resources</h3>
|
||||
<ul className="mt-6 space-y-4">
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DOCUMENTATION}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Documentation
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.TUTORIALS}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Tutorials
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.COMMUNITY}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Community
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DISCORD}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Discord
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.REDDIT}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Reddit
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:grid md:grid-cols-2 md:gap-8">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold uppercase leading-6 text-foreground">Support</h3>
|
||||
<ul className="mt-6 space-y-4">
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.ISSUES}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Issues
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.FEATURE_REQUESTS}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Feature Requests
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<ScrollButton
|
||||
targetId="faq"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
FAQ
|
||||
</ScrollButton>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-10 md:mt-0">
|
||||
<h3 className="text-sm font-semibold uppercase leading-6 text-foreground">Company</h3>
|
||||
<ul className="mt-6 space-y-4">
|
||||
<li>
|
||||
<a
|
||||
href="mailto:support@roocode.com"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Contact
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.CAREERS}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Careers
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<div className="relative z-10" ref={dropdownRef}>
|
||||
<button
|
||||
onClick={() => setPrivacyDropdownOpen(!privacyDropdownOpen)}
|
||||
className="flex items-center text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground"
|
||||
aria-expanded={privacyDropdownOpen}
|
||||
aria-haspopup="true">
|
||||
<span>
|
||||
Privacy <span className="max-[320px]:hidden">Policy</span>
|
||||
</span>
|
||||
<ChevronDown
|
||||
className={`ml-1 h-4 w-4 transition-transform ${privacyDropdownOpen ? "rotate-180" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{privacyDropdownOpen && (
|
||||
<div className="absolute z-50 mt-2 w-44 origin-top-left scale-95 rounded-md border border-border bg-background shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-100 ease-out data-[state=open]:scale-100 max-xs:right-0 max-xs:origin-top-right xs:left-0">
|
||||
<div className="flex flex-col gap-1 p-2 text-sm text-muted-foreground">
|
||||
<a
|
||||
href={EXTERNAL_LINKS.PRIVACY_POLICY_EXTENSION}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => setPrivacyDropdownOpen(false)}
|
||||
className="rounded-md px-3 py-2 transition-colors hover:bg-accent/50 hover:text-foreground">
|
||||
Extension
|
||||
</a>
|
||||
<Link
|
||||
href={INTERNAL_LINKS.PRIVACY_POLICY_WEBSITE}
|
||||
onClick={() => setPrivacyDropdownOpen(false)}
|
||||
className="rounded-md px-3 py-2 transition-colors hover:bg-accent/50 hover:text-foreground">
|
||||
Marketing Website
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-16 flex border-t border-border pt-8 sm:mt-20 lg:mt-24">
|
||||
<p className="mx-auto text-sm leading-5 text-muted-foreground">
|
||||
© {new Date().getFullYear()} Roo Code. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
export * from "./footer"
|
||||
export * from "./nav-bar"
|
||||
export * from "./stats-display"
|
||||
export * from "./theme-toggle"
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
/* eslint-disable react/jsx-no-target-blank */
|
||||
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import Image from "next/image"
|
||||
import { useState } from "react"
|
||||
import { RxGithubLogo } from "react-icons/rx"
|
||||
import { VscVscode } from "react-icons/vsc"
|
||||
import { HiMenu } from "react-icons/hi"
|
||||
import { IoClose } from "react-icons/io5"
|
||||
|
||||
import { EXTERNAL_LINKS } from "@/lib/constants"
|
||||
import { useLogoSrc } from "@/lib/hooks/use-logo-src"
|
||||
import { ScrollButton } from "@/components/ui"
|
||||
import ThemeToggle from "@/components/chromes/theme-toggle"
|
||||
|
||||
interface NavBarProps {
|
||||
stars: string | null
|
||||
downloads: string | null
|
||||
}
|
||||
|
||||
export function NavBar({ stars, downloads }: NavBarProps) {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
||||
const logoSrc = useLogoSrc()
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 border-b border-border bg-background/80 backdrop-blur-md">
|
||||
<div className="container mx-auto flex h-16 items-center justify-between px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center">
|
||||
<Link href="/" className="flex items-center">
|
||||
<Image src={logoSrc} alt="Roo Code Logo" width={120} height={40} className="h-8 w-auto" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden text-sm font-medium md:flex md:items-center md:space-x-3 xl:space-x-8">
|
||||
{/* note: features and testimonials links are hidden for screen sizes smaller than lg */}
|
||||
<ScrollButton
|
||||
targetId="features"
|
||||
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground max-lg:hidden">
|
||||
Features
|
||||
</ScrollButton>
|
||||
<ScrollButton
|
||||
targetId="testimonials"
|
||||
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground max-lg:hidden">
|
||||
Testimonials
|
||||
</ScrollButton>
|
||||
<ScrollButton
|
||||
targetId="faq"
|
||||
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
FAQ
|
||||
</ScrollButton>
|
||||
<Link
|
||||
href="/evals"
|
||||
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Evals
|
||||
</Link>
|
||||
<Link
|
||||
href="/enterprise"
|
||||
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Enterprise
|
||||
</Link>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DOCUMENTATION}
|
||||
target="_blank"
|
||||
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Documentation
|
||||
</a>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.CAREERS}
|
||||
target="_blank"
|
||||
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Careers
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div className="hidden md:flex md:items-center md:space-x-4">
|
||||
<div className="flex flex-row space-x-2">
|
||||
<ThemeToggle />
|
||||
<Link
|
||||
href={EXTERNAL_LINKS.GITHUB}
|
||||
target="_blank"
|
||||
className="hidden items-center gap-1.5 text-sm font-medium text-muted-foreground hover:text-foreground md:flex">
|
||||
<RxGithubLogo className="h-4 w-4" />
|
||||
{stars !== null && <span>{stars}</span>}
|
||||
</Link>
|
||||
</div>
|
||||
<Link
|
||||
href={EXTERNAL_LINKS.MARKETPLACE}
|
||||
target="_blank"
|
||||
className="hidden items-center gap-1.5 rounded-full bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 md:flex">
|
||||
<VscVscode className="-mr-[2px] mt-[1px] h-4 w-4" />
|
||||
<span>
|
||||
Install <span className="font-black max-lg:text-xs">·</span>
|
||||
</span>
|
||||
{downloads !== null && <span>{downloads}</span>}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<button
|
||||
aria-expanded={isMenuOpen}
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
className="flex items-center justify-center rounded-full p-2 transition-colors hover:bg-accent md:hidden"
|
||||
aria-label="Toggle mobile menu">
|
||||
{isMenuOpen ? <IoClose className="h-6 w-6" /> : <HiMenu className="h-6 w-6" />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Panel */}
|
||||
<div
|
||||
className={`absolute left-0 right-0 top-16 z-50 transform border-b border-border bg-background shadow-lg backdrop-blur-none transition-all duration-200 md:hidden ${isMenuOpen ? "translate-y-0 opacity-100" : "pointer-events-none -translate-y-2 opacity-0"}`}>
|
||||
<nav className="flex flex-col py-2">
|
||||
<ScrollButton
|
||||
targetId="features"
|
||||
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Features
|
||||
</ScrollButton>
|
||||
<ScrollButton
|
||||
targetId="testimonials"
|
||||
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Testimonials
|
||||
</ScrollButton>
|
||||
<ScrollButton
|
||||
targetId="faq"
|
||||
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
FAQ
|
||||
</ScrollButton>
|
||||
<Link
|
||||
href="/evals"
|
||||
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Evals
|
||||
</Link>
|
||||
<Link
|
||||
href="/enterprise"
|
||||
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Enterprise
|
||||
</Link>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DOCUMENTATION}
|
||||
target="_blank"
|
||||
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Documentation
|
||||
</a>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.CAREERS}
|
||||
target="_blank"
|
||||
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Careers
|
||||
</a>
|
||||
|
||||
<hr className="mx-8 my-2 border-t border-border/50" />
|
||||
|
||||
{/* Icons & Stats */}
|
||||
<div className="flex items-center justify-center gap-8 px-8 py-3">
|
||||
<Link
|
||||
href={EXTERNAL_LINKS.GITHUB}
|
||||
target="_blank"
|
||||
className="inline-flex items-center gap-2 rounded-md p-2 text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
<RxGithubLogo className="h-5 w-5" />
|
||||
{stars !== null && <span>{stars}</span>}
|
||||
</Link>
|
||||
<div className="flex items-center rounded-md p-2 transition-colors hover:bg-accent">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
<Link
|
||||
href={EXTERNAL_LINKS.MARKETPLACE}
|
||||
target="_blank"
|
||||
className="inline-flex items-center gap-2 rounded-md p-2 text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
<VscVscode className="h-5 w-5" />
|
||||
{downloads !== null && <span>{downloads}</span>}
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import Link from "next/link"
|
||||
import { RxGithubLogo } from "react-icons/rx"
|
||||
import { VscVscode } from "react-icons/vsc"
|
||||
import { getGitHubStars, getVSCodeDownloads } from "@/lib/stats"
|
||||
|
||||
export default async function StatsDisplay() {
|
||||
const stars = await getGitHubStars()
|
||||
const downloads = await getVSCodeDownloads()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Link
|
||||
href="https://github.com/RooVetGit/Roo-Code"
|
||||
target="_blank"
|
||||
className="hidden md:flex items-center gap-1.5 text-sm font-medium text-gray-400 hover:text-gray-200">
|
||||
<RxGithubLogo className="h-4 w-4" />
|
||||
{stars !== null && <span>{stars}</span>}
|
||||
</Link>
|
||||
<Link
|
||||
href="https://marketplace.visualstudio.com/items?itemName=RooVeterinaryInc.roo-cline"
|
||||
target="_blank"
|
||||
className="hidden md:flex items-center gap-1.5 rounded-full bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90">
|
||||
<VscVscode className="h-4 w-4" />
|
||||
<span>
|
||||
Install <span className="font-black">·</span>
|
||||
</span>
|
||||
{downloads !== null && <span>{downloads}</span>}
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { useTheme } from "next-themes"
|
||||
import { RxSun, RxMoon } from "react-icons/rx"
|
||||
|
||||
import { Button } from "@/components/ui"
|
||||
|
||||
export default function ThemeToggle() {
|
||||
const { theme, setTheme } = useTheme()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
// Avoid hydration mismatch.
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
if (!mounted) {
|
||||
return (
|
||||
<Button variant="ghost" size="icon" disabled className="h-9 w-9">
|
||||
<RxSun className="h-4 w-4" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
||||
className="h-9 w-9"
|
||||
aria-label="Toggle theme">
|
||||
{theme === "dark" ? (
|
||||
<RxSun className="h-4 w-4 transition-all" />
|
||||
) : (
|
||||
<RxMoon className="h-4 w-4 transition-all" />
|
||||
)}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,291 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useState, useRef } from "react"
|
||||
import { z } from "zod"
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui"
|
||||
|
||||
// Define the form schema using Zod
|
||||
const contactFormSchema = z.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
company: z.string().min(1, "Company is required"),
|
||||
email: z.string().email("Invalid email address"),
|
||||
website: z.string().url("Invalid website URL").or(z.string().length(0)),
|
||||
engineerCount: z.enum(["1-10", "11-50", "51-200", "201-500", "501-1000", "1000+"]),
|
||||
formType: z.enum(["early-access", "demo"]),
|
||||
_honeypot: z.string().optional(),
|
||||
})
|
||||
|
||||
interface ContactFormProps {
|
||||
formType: "early-access" | "demo"
|
||||
buttonText: string
|
||||
buttonClassName?: string
|
||||
}
|
||||
|
||||
export function ContactForm({ formType, buttonText, buttonClassName }: ContactFormProps) {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [formErrors, setFormErrors] = useState<Record<string, string>>({})
|
||||
const [submitStatus, setSubmitStatus] = useState<"idle" | "success" | "error">("idle")
|
||||
const formRef = useRef<HTMLFormElement>(null)
|
||||
|
||||
const formTitle = formType === "early-access" ? "Become an Early Access Partner" : "Request a Demo"
|
||||
|
||||
const formDescription =
|
||||
formType === "early-access"
|
||||
? "Fill out the form below to collaborate in shaping Roo Code's enterprise solution."
|
||||
: "Fill out the form below to see Roo Code's enterprise capabilities in action."
|
||||
|
||||
// Get Basin endpoint from environment variable
|
||||
// This should be set in .env.local as NEXT_PUBLIC_BASIN_ENDPOINT="https://usebasin.com/f/your-form-id"
|
||||
const BASIN_ENDPOINT = process.env.NEXT_PUBLIC_BASIN_ENDPOINT || ""
|
||||
|
||||
// Check if Basin endpoint is configured
|
||||
if (!BASIN_ENDPOINT) {
|
||||
console.warn("NEXT_PUBLIC_BASIN_ENDPOINT is not configured. Form submissions will not work.")
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
setIsSubmitting(true)
|
||||
setFormErrors({})
|
||||
setSubmitStatus("idle")
|
||||
|
||||
const form = e.currentTarget
|
||||
const formData = new FormData(form)
|
||||
|
||||
// Create a data object for validation and submission
|
||||
const data = {
|
||||
name: formData.get("name") as string,
|
||||
company: formData.get("company") as string,
|
||||
email: formData.get("email") as string,
|
||||
website: formData.get("website") as string,
|
||||
engineerCount: formData.get("engineerCount") as string,
|
||||
formType,
|
||||
// Include honeypot field for spam protection
|
||||
_honeypot: formData.get("_honeypot") as string,
|
||||
}
|
||||
|
||||
// Validate form data on client side
|
||||
try {
|
||||
contactFormSchema.parse(data)
|
||||
|
||||
// Submit data to Basin
|
||||
try {
|
||||
const response = await fetch(BASIN_ENDPOINT, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
mode: "cors", // Ensure proper CORS handling
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
// Basin returns a 200 status code on success
|
||||
if (response.ok) {
|
||||
try {
|
||||
const responseData = await response.json()
|
||||
|
||||
// Basin JSON API typically returns a 'status' property of 'success' when submission succeeds
|
||||
if (responseData && (responseData.success === true || responseData.status === "success")) {
|
||||
setSubmitStatus("success")
|
||||
// Reset form safely
|
||||
if (form) {
|
||||
form.reset()
|
||||
}
|
||||
} else {
|
||||
console.error("Basin error:", responseData)
|
||||
setSubmitStatus("error")
|
||||
}
|
||||
} catch (jsonError) {
|
||||
// In case response parsing fails but status was OK, assume success
|
||||
console.error("Error parsing JSON response:", jsonError)
|
||||
setSubmitStatus("success")
|
||||
if (form) {
|
||||
form.reset()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle error response from Basin (4xx or 5xx)
|
||||
try {
|
||||
const errorData = await response.json()
|
||||
console.error("Basin API error:", response.status, errorData)
|
||||
} catch {
|
||||
console.error("Basin returned error status:", response.status)
|
||||
}
|
||||
setSubmitStatus("error")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error submitting form:", error)
|
||||
setSubmitStatus("error")
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
const errors: Record<string, string> = {}
|
||||
error.errors.forEach((err) => {
|
||||
if (err.path[0]) {
|
||||
errors[err.path[0] as string] = err.message
|
||||
}
|
||||
})
|
||||
setFormErrors(errors)
|
||||
} else {
|
||||
setSubmitStatus("error")
|
||||
}
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className={buttonClassName}>{buttonText}</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{formTitle}</DialogTitle>
|
||||
<DialogDescription>{formDescription}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{submitStatus === "success" ? (
|
||||
<div className="flex flex-col items-center justify-center py-6">
|
||||
<div className="mb-4 rounded-full bg-green-100 p-3 text-green-600 dark:bg-green-900/20 dark:text-green-400">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="mb-2 text-xl font-bold">Thank You!</h3>
|
||||
<p className="text-center text-muted-foreground">
|
||||
Your information has been submitted successfully. Our team will be in touch with you
|
||||
shortly.
|
||||
</p>
|
||||
<Button className="mt-4" onClick={() => setIsOpen(false)}>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<form ref={formRef} onSubmit={handleSubmit} className="space-y-4" data-basin-form>
|
||||
{/* Basin honeypot field for spam protection - should remain empty and hidden */}
|
||||
<input type="text" name="_honeypot" className="hidden" style={{ display: "none" }} />
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="name" className="text-sm font-medium">
|
||||
Name <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
className={`w-full rounded-md border ${
|
||||
formErrors.name ? "border-red-500" : "border-input"
|
||||
} bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring`}
|
||||
placeholder="Your name"
|
||||
/>
|
||||
{formErrors.name && <p className="text-xs text-red-500">{formErrors.name}</p>}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="company" className="text-sm font-medium">
|
||||
Company <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="company"
|
||||
name="company"
|
||||
type="text"
|
||||
className={`w-full rounded-md border ${
|
||||
formErrors.company ? "border-red-500" : "border-input"
|
||||
} bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring`}
|
||||
placeholder="Your company"
|
||||
/>
|
||||
{formErrors.company && <p className="text-xs text-red-500">{formErrors.company}</p>}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="email" className="text-sm font-medium">
|
||||
Email <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
className={`w-full rounded-md border ${
|
||||
formErrors.email ? "border-red-500" : "border-input"
|
||||
} bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring`}
|
||||
placeholder="your.email@example.com"
|
||||
/>
|
||||
{formErrors.email && <p className="text-xs text-red-500">{formErrors.email}</p>}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="website" className="text-sm font-medium">
|
||||
Website
|
||||
</label>
|
||||
<input
|
||||
id="website"
|
||||
name="website"
|
||||
type="url"
|
||||
className={`w-full rounded-md border ${
|
||||
formErrors.website ? "border-red-500" : "border-input"
|
||||
} bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring`}
|
||||
placeholder="https://example.com"
|
||||
/>
|
||||
{formErrors.website && <p className="text-xs text-red-500">{formErrors.website}</p>}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="engineerCount" className="text-sm font-medium">
|
||||
Number of Software Engineers <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
id="engineerCount"
|
||||
name="engineerCount"
|
||||
className={`w-full rounded-md border ${
|
||||
formErrors.engineerCount ? "border-red-500" : "border-input"
|
||||
} bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring`}
|
||||
defaultValue="1-10">
|
||||
<option value="1-10">1-10</option>
|
||||
<option value="11-50">11-50</option>
|
||||
<option value="51-200">51-200</option>
|
||||
<option value="201-500">201-500</option>
|
||||
<option value="501-1000">501-1000</option>
|
||||
<option value="1000+">1000+</option>
|
||||
</select>
|
||||
{formErrors.engineerCount && (
|
||||
<p className="text-xs text-red-500">{formErrors.engineerCount}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{submitStatus === "error" && (
|
||||
<div className="rounded-md bg-red-50 p-3 text-sm text-red-500 dark:bg-red-900/20">
|
||||
There was an error submitting your request. Please try again later.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={() => setIsOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting ? "Submitting..." : "Submit"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,280 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useEffect, useRef } from "react"
|
||||
|
||||
export function AnimatedBackground() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current
|
||||
if (!canvas) return
|
||||
|
||||
const ctx = canvas.getContext("2d")
|
||||
if (!ctx) return
|
||||
|
||||
// grid settings
|
||||
const gridSize = 50
|
||||
const gridOpacity = 0.15
|
||||
|
||||
// initialize gradient points for lighting effects
|
||||
let gradientPoints = [
|
||||
{
|
||||
x: canvas.width * 0.2,
|
||||
y: canvas.height * 0.3,
|
||||
radius: canvas.width * 0.4,
|
||||
color: "rgba(0, 100, 255, 0.15)",
|
||||
},
|
||||
{
|
||||
x: canvas.width * 0.8,
|
||||
y: canvas.height * 0.7,
|
||||
radius: canvas.width * 0.5,
|
||||
color: "rgba(100, 0, 255, 0.1)",
|
||||
},
|
||||
]
|
||||
|
||||
// particle system
|
||||
const particles: Particle[] = []
|
||||
const particleCount = Math.min(50, Math.floor(window.innerWidth / 40))
|
||||
|
||||
// set canvas dimensions
|
||||
const resizeCanvas = () => {
|
||||
canvas.width = window.innerWidth
|
||||
canvas.height = window.innerHeight
|
||||
|
||||
// update gradient points when canvas is resized
|
||||
gradientPoints = [
|
||||
{
|
||||
x: canvas.width * 0.2,
|
||||
y: canvas.height * 0.3,
|
||||
radius: canvas.width * 0.4,
|
||||
color: "rgba(0, 100, 255, 0.15)",
|
||||
},
|
||||
{
|
||||
x: canvas.width * 0.8,
|
||||
y: canvas.height * 0.7,
|
||||
radius: canvas.width * 0.5,
|
||||
color: "rgba(100, 0, 255, 0.1)",
|
||||
},
|
||||
]
|
||||
|
||||
// redraw grid after resize
|
||||
drawGrid()
|
||||
}
|
||||
|
||||
resizeCanvas()
|
||||
window.addEventListener("resize", resizeCanvas)
|
||||
|
||||
// draw grid with perspective effect
|
||||
function drawGrid() {
|
||||
if (!ctx) {
|
||||
throw new Error("Context is null (not initialized?)")
|
||||
}
|
||||
|
||||
if (!canvas) {
|
||||
throw new Error("Canvas is null (not initialized?)")
|
||||
}
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// Draw gradient lighting effects.
|
||||
gradientPoints.forEach((point) => {
|
||||
const gradient = ctx.createRadialGradient(point.x, point.y, 0, point.x, point.y, point.radius)
|
||||
gradient.addColorStop(0, point.color)
|
||||
gradient.addColorStop(1, "rgba(0, 0, 0, 0)")
|
||||
|
||||
ctx.fillStyle = gradient
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||
})
|
||||
|
||||
// Draw grid lines with perspective effect.
|
||||
ctx.strokeStyle = `rgba(50, 50, 70, ${gridOpacity})`
|
||||
ctx.lineWidth = 0.5
|
||||
|
||||
// horizontal lines with perspective.
|
||||
const horizonY = canvas.height * 0.7 // Horizon point.
|
||||
const vanishingPointX = canvas.width * 0.5 // Center vanishing point.
|
||||
|
||||
// Vertical lines.
|
||||
for (let x = 0; x <= canvas.width; x += gridSize) {
|
||||
const normalizedX = x / canvas.width - 0.5 // -0.5 to 0.5
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x, 0)
|
||||
|
||||
// Calculate curve based on distance from center.
|
||||
const curveStrength = 50 * Math.abs(normalizedX)
|
||||
const controlPointY = horizonY - curveStrength
|
||||
|
||||
// Create curved line toward vanishing point.
|
||||
ctx.quadraticCurveTo(
|
||||
x + (vanishingPointX - x) * 0.3,
|
||||
controlPointY,
|
||||
vanishingPointX + (x - vanishingPointX) * 0.2,
|
||||
horizonY,
|
||||
)
|
||||
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
// Horizontal lines.
|
||||
for (let y = 0; y <= horizonY; y += gridSize) {
|
||||
const normalizedY = y / horizonY // 0 to 1
|
||||
const lineWidth = gridSize * (1 + normalizedY * 5) // lines get wider as they get closer
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(vanishingPointX - lineWidth, y)
|
||||
ctx.lineTo(vanishingPointX + lineWidth, y)
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
updateParticles()
|
||||
}
|
||||
|
||||
class Particle {
|
||||
x: number
|
||||
y: number
|
||||
size: number
|
||||
speedX: number
|
||||
speedY: number
|
||||
color: string
|
||||
opacity: number
|
||||
|
||||
constructor() {
|
||||
if (!canvas) {
|
||||
throw new Error("Canvas is null (not initialized?)")
|
||||
}
|
||||
|
||||
this.x = Math.random() * canvas.width
|
||||
this.y = Math.random() * (canvas.height * 0.7) // Keep particles above horizon.
|
||||
this.size = Math.random() * 2 + 1
|
||||
this.speedX = (Math.random() - 0.5) * 0.8
|
||||
this.speedY = (Math.random() - 0.5) * 0.8
|
||||
this.color = "rgba(100, 150, 255, "
|
||||
this.opacity = Math.random() * 0.5 + 0.2
|
||||
}
|
||||
|
||||
update() {
|
||||
if (!canvas) {
|
||||
throw new Error("Canvas is null (not initialized?)")
|
||||
}
|
||||
|
||||
this.x += this.speedX
|
||||
this.y += this.speedY
|
||||
|
||||
// Boundary check.
|
||||
if (this.x > canvas.width) this.x = 0
|
||||
else if (this.x < 0) this.x = canvas.width
|
||||
if (this.y > canvas.height * 0.7) this.y = 0
|
||||
else if (this.y < 0) this.y = canvas.height * 0.7
|
||||
|
||||
// Pulsate opacity.
|
||||
this.opacity += Math.sin(Date.now() * 0.001) * 0.01
|
||||
this.opacity = Math.max(0.1, Math.min(0.7, this.opacity))
|
||||
}
|
||||
|
||||
draw() {
|
||||
if (!ctx) {
|
||||
throw new Error("Context is null (not initialized?)")
|
||||
}
|
||||
|
||||
ctx.fillStyle = `${this.color}${this.opacity})`
|
||||
ctx.beginPath()
|
||||
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize particles.
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
particles.push(new Particle())
|
||||
}
|
||||
|
||||
// Connect particles with lines.
|
||||
function connectParticles() {
|
||||
if (!ctx) {
|
||||
throw new Error("Context is null (not initialized?)")
|
||||
}
|
||||
|
||||
const maxDistance = 150
|
||||
|
||||
for (let a = 0; a < particles.length; a++) {
|
||||
for (let b = a; b < particles.length; b++) {
|
||||
const dx = particles[a]!.x - particles[b]!.x
|
||||
const dy = particles[a]!.y - particles[b]!.y
|
||||
const distance = Math.sqrt(dx * dx + dy * dy)
|
||||
|
||||
if (distance < maxDistance) {
|
||||
const opacity = (1 - distance / maxDistance) * 0.5
|
||||
ctx.strokeStyle = `rgba(100, 150, 255, ${opacity})`
|
||||
ctx.lineWidth = 0.5
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(particles[a]!.x, particles[a]!.y)
|
||||
ctx.lineTo(particles[b]!.x, particles[b]!.y)
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateParticles() {
|
||||
particles.forEach((particle) => {
|
||||
particle.update()
|
||||
particle.draw()
|
||||
})
|
||||
|
||||
connectParticles()
|
||||
}
|
||||
|
||||
// Animation loop.
|
||||
let animationId: number
|
||||
|
||||
// Target position for smooth following.
|
||||
let targetX = canvas.width * 0.2
|
||||
let targetY = canvas.height * 0.3
|
||||
const moveSpeed = 0.05 // Adjust this value to control movement speed (0-1).
|
||||
|
||||
// Move gradient points with mouse.
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
targetX = e.clientX
|
||||
targetY = e.clientY
|
||||
}
|
||||
|
||||
// Update gradient point position in animation loop.
|
||||
function updateGradientPosition() {
|
||||
if (!canvas) throw new Error("Canvas is null (not initialized?)")
|
||||
|
||||
// Calculate direction vector.
|
||||
const dx = targetX - gradientPoints[0]!.x
|
||||
const dy = targetY - gradientPoints[0]!.y
|
||||
|
||||
// Smooth movement using linear interpolation.
|
||||
gradientPoints[0]!.x += dx * moveSpeed
|
||||
gradientPoints[0]!.y += dy * moveSpeed
|
||||
|
||||
// Adjust radius based on distance to target.
|
||||
const distanceToTarget = Math.sqrt(dx * dx + dy * dy)
|
||||
gradientPoints[0]!.radius = Math.max(
|
||||
canvas.width * 0.2,
|
||||
Math.min(canvas.width * 0.4, canvas.width * 0.3 + distanceToTarget * 0.1),
|
||||
)
|
||||
}
|
||||
|
||||
function animate() {
|
||||
animationId = requestAnimationFrame(animate)
|
||||
updateGradientPosition()
|
||||
drawGrid()
|
||||
}
|
||||
|
||||
animate()
|
||||
|
||||
window.addEventListener("mousemove", handleMouseMove)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", resizeCanvas)
|
||||
window.removeEventListener("mousemove", handleMouseMove)
|
||||
cancelAnimationFrame(animationId)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return <canvas ref={canvasRef} className="absolute inset-0 h-full w-full" style={{ zIndex: 0 }} />
|
||||
}
|
||||
|
|
@ -1,236 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useState, useEffect, useRef } from "react"
|
||||
import { motion } from "framer-motion"
|
||||
|
||||
export function CodeExample() {
|
||||
const [currentMode, setCurrentMode] = useState<"code" | "architect" | "debug">("code")
|
||||
const [isTyping, setIsTyping] = useState(false)
|
||||
const [currentText, setCurrentText] = useState("")
|
||||
const [textIndex, setTextIndex] = useState(0)
|
||||
const codeContainerRef = useRef<HTMLPreElement>(null)
|
||||
|
||||
// simulate typing effect
|
||||
useEffect(() => {
|
||||
if (isTyping && textIndex < codeExamples[currentMode].code.length) {
|
||||
const timer = setTimeout(() => {
|
||||
setCurrentText((prev) => prev + codeExamples[currentMode].code[textIndex])
|
||||
setTextIndex(textIndex + 1)
|
||||
|
||||
// Auto-scroll to the bottom
|
||||
if (codeContainerRef.current) {
|
||||
codeContainerRef.current.scrollTop = codeContainerRef.current.scrollHeight
|
||||
}
|
||||
}, 15) // adjust speed as needed
|
||||
return () => clearTimeout(timer)
|
||||
} else if (textIndex >= codeExamples[currentMode].code.length) {
|
||||
setIsTyping(false)
|
||||
// switch to next mode after a delay
|
||||
const timer = setTimeout(() => {
|
||||
const nextMode = currentMode === "code" ? "architect" : currentMode === "architect" ? "debug" : "code"
|
||||
switchMode(nextMode)
|
||||
}, 1000) // wait a second before switching
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [isTyping, textIndex, currentMode])
|
||||
|
||||
// switch modes with typing effect
|
||||
const switchMode = (mode: "code" | "architect" | "debug") => {
|
||||
setCurrentMode(mode)
|
||||
setCurrentText("")
|
||||
setTextIndex(0)
|
||||
setIsTyping(true)
|
||||
|
||||
// Reset scroll position when switching modes
|
||||
if (codeContainerRef.current) {
|
||||
codeContainerRef.current.scrollTop = 0
|
||||
}
|
||||
}
|
||||
|
||||
// start typing on initial load
|
||||
useEffect(() => {
|
||||
setIsTyping(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="relative z-10 w-full max-w-[90vw] rounded-lg border border-border bg-background/50 p-2 shadow-2xl backdrop-blur-sm sm:max-w-[500px]">
|
||||
<div className="rounded-md bg-muted p-1.5 dark:bg-gray-900 sm:p-2">
|
||||
<div className="flex items-center justify-between border-b border-border px-2 py-1.5 sm:px-3 sm:py-2">
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<div className="h-2.5 w-2.5 rounded-full bg-red-500 sm:h-3 sm:w-3" />
|
||||
<div className="h-2.5 w-2.5 rounded-full bg-yellow-500 sm:h-3 sm:w-3" />
|
||||
<div className="h-2.5 w-2.5 rounded-full bg-green-500 sm:h-3 sm:w-3" />
|
||||
</div>
|
||||
<div className="flex space-x-1">
|
||||
<button
|
||||
onClick={() => switchMode("code")}
|
||||
className={`rounded px-2 py-0.5 text-xs font-medium transition-colors sm:text-sm ${currentMode === "code" ? "bg-blue-500/20 text-blue-400" : "text-gray-400 hover:bg-gray-800"}`}>
|
||||
Code
|
||||
</button>
|
||||
<button
|
||||
onClick={() => switchMode("architect")}
|
||||
className={`rounded px-2 py-0.5 text-xs font-medium transition-colors sm:text-sm ${currentMode === "architect" ? "bg-purple-500/20 text-purple-400" : "text-gray-400 hover:bg-gray-800"}`}>
|
||||
Architect
|
||||
</button>
|
||||
<button
|
||||
onClick={() => switchMode("debug")}
|
||||
className={`rounded px-2 py-0.5 text-xs font-medium transition-colors sm:text-sm ${currentMode === "debug" ? "bg-green-500/20 text-green-400" : "text-gray-400 hover:bg-gray-800"}`}>
|
||||
Debug
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-2 sm:p-4">
|
||||
<pre
|
||||
ref={codeContainerRef}
|
||||
className="scrollbar-thin scrollbar-thumb-gray-700 scrollbar-track-transparent overflow-y-hidden text-xs text-muted-foreground max-lg:h-[25vh] sm:text-sm lg:max-h-[50vh]">
|
||||
<code className="block whitespace-pre font-mono">
|
||||
{currentText}
|
||||
{isTyping && (
|
||||
<motion.span
|
||||
animate={{ opacity: [1, 0] }}
|
||||
transition={{ repeat: Number.POSITIVE_INFINITY, duration: 0.8 }}
|
||||
className="inline-block h-4 w-2 bg-blue-500"
|
||||
/>
|
||||
)}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const codeExamples = {
|
||||
code: {
|
||||
code: `// Code Mode
|
||||
You: Generate a React component for a user profile card
|
||||
|
||||
Roo: Here's a React component for a user profile card:
|
||||
|
||||
import React from 'react';
|
||||
|
||||
interface UserProfileProps {
|
||||
name: string;
|
||||
role: string;
|
||||
avatar: string;
|
||||
bio: string;
|
||||
stats: {
|
||||
projects: number;
|
||||
followers: number;
|
||||
following: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const UserProfile: React.FC<UserProfileProps> = ({
|
||||
name,
|
||||
role,
|
||||
avatar,
|
||||
bio,
|
||||
stats
|
||||
}) => {
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow-md p-6 max-w-sm">
|
||||
<div className="flex items-center space-x-4">
|
||||
<img
|
||||
src={avatar || "/placeholder.svg"}
|
||||
alt={name}
|
||||
className="w-16 h-16 rounded-full object-cover"
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-xl font-bold">{name}</h3>
|
||||
<p className="text-gray-600">{role}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-4 text-gray-700">{bio}</p>
|
||||
<div className="mt-6 flex justify-between">
|
||||
<div className="text-center">
|
||||
<span className="block font-bold">{stats.projects}</span>
|
||||
<span className="text-sm text-gray-500">Projects</span>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<span className="block font-bold">{stats.followers}</span>
|
||||
<span className="text-sm text-gray-500">Followers</span>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<span className="block font-bold">{stats.following}</span>
|
||||
<span className="text-sm text-gray-500">Following</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};`,
|
||||
},
|
||||
architect: {
|
||||
code: `// Architect Mode
|
||||
// Project: E-commerce Platform
|
||||
// Request: Design the authentication system
|
||||
|
||||
Roo is analyzing your project...
|
||||
|
||||
📂 Recommended Architecture:
|
||||
|
||||
auth/
|
||||
├─ services/
|
||||
│ ├─ authService.ts // Core authentication logic
|
||||
│ ├─ tokenService.ts // JWT handling and refresh
|
||||
│ └─ userService.ts // User data operations
|
||||
├─ hooks/
|
||||
│ ├─ useAuth.ts // React hook for auth state
|
||||
│ └─ useProtectedRoute.ts // Route protection
|
||||
├─ components/
|
||||
│ ├─ LoginForm.tsx // Login UI
|
||||
│ ├─ SignupForm.tsx // Registration UI
|
||||
│ ├─ PasswordReset.tsx // Password recovery
|
||||
│ └─ TwoFactorAuth.tsx // 2FA implementation
|
||||
└─ context/
|
||||
└─ AuthContext.tsx // Global auth state
|
||||
|
||||
🔐 Security Recommendations:
|
||||
- Implement PKCE flow for auth code exchange
|
||||
- Use HttpOnly cookies for refresh tokens
|
||||
- Rate limit authentication attempts
|
||||
- Add device fingerprinting for suspicious login detection
|
||||
|
||||
⚡ Performance Considerations:
|
||||
- Prefetch user data on auth
|
||||
- Implement token refresh without UI disruption
|
||||
- Lazy load auth components
|
||||
|
||||
Would you like me to generate any of these files?`,
|
||||
},
|
||||
debug: {
|
||||
code: `// Debug Mode
|
||||
// Analyzing error: TypeError: Cannot read property 'map' of undefined
|
||||
|
||||
Roo has analyzed your code and found 3 issues:
|
||||
|
||||
🐛 Issue #1: Null data reference
|
||||
Line 42: const items = data.items.map(item => item.name);
|
||||
|
||||
✓ Root Cause: 'data' is undefined when component mounts
|
||||
✓ Context: API request in useEffect hasn't completed yet
|
||||
|
||||
Recommended Fix:
|
||||
const items = data?.items?.map(item => item.name) || [];
|
||||
|
||||
🐛 Issue #2: Missing dependency in useEffect
|
||||
Line 37: useEffect(() => { fetchData() }, []);
|
||||
|
||||
✓ Root Cause: fetchData depends on 'userId' but isn't in deps array
|
||||
✓ Context: This causes stale data when userId changes
|
||||
|
||||
Recommended Fix:
|
||||
useEffect(() => { fetchData() }, [userId, fetchData]);
|
||||
|
||||
🐛 Issue #3: Memory leak from unfinished API call
|
||||
Line 38: const response = await api.getItems(userId);
|
||||
|
||||
✓ Root Cause: No cleanup when component unmounts during API call
|
||||
✓ Context: This triggers React warning in development
|
||||
|
||||
Recommended Fix:
|
||||
Add AbortController to cancel pending requests on unmount
|
||||
|
||||
Apply these fixes automatically? [Yes/No]`,
|
||||
},
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
|
||||
export function CompanyLogos() {
|
||||
const logos = [
|
||||
{ name: "Company 1", logo: "/placeholder.svg?height=40&width=120" },
|
||||
{ name: "Company 2", logo: "/placeholder.svg?height=40&width=120" },
|
||||
{ name: "Company 3", logo: "/placeholder.svg?height=40&width=120" },
|
||||
{ name: "Company 4", logo: "/placeholder.svg?height=40&width=120" },
|
||||
{ name: "Company 5", logo: "/placeholder.svg?height=40&width=120" },
|
||||
{ name: "Company 6", logo: "/placeholder.svg?height=40&width=120" },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="mt-10">
|
||||
<div className="mx-auto grid max-w-5xl grid-cols-2 gap-8 py-8 md:grid-cols-3 lg:grid-cols-6">
|
||||
{logos.map((company, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
delay: index * 0.1,
|
||||
ease: "easeOut",
|
||||
}}
|
||||
className="flex items-center justify-center">
|
||||
{/* eslint-disable @next/next/no-img-element */}
|
||||
<img
|
||||
src={company.logo || "/placeholder.svg"}
|
||||
alt={company.name}
|
||||
className="h-10 w-auto opacity-70 grayscale transition-all duration-300 hover:opacity-100 hover:grayscale-0"
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { motion } from "framer-motion"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface FAQItem {
|
||||
question: string
|
||||
answer: string
|
||||
}
|
||||
|
||||
const faqs: FAQItem[] = [
|
||||
{
|
||||
question: "What exactly is Roo Code?",
|
||||
answer: "Roo Code is an open-source, AI-powered coding assistant that runs in VS Code. It goes beyond simple autocompletion by reading and writing across multiple files, executing commands, and adapting to your workflow—like having a whole dev team right inside your editor.",
|
||||
},
|
||||
{
|
||||
question: "How does Roo Code differ from Copilot, Cursor, or Windsurf?",
|
||||
answer: "Open & Customizable: Roo Code is open-source and allows you to integrate any AI model (OpenAI, Anthropic, local LLMs, etc.). Multi-File Edits: It can read, refactor, and update multiple files at once for more holistic changes. Agentic Abilities: Roo Code can run tests, open a browser, or do deeper tasks than a typical AI autocomplete. Permission-Based: You control and approve any file changes or command executions.",
|
||||
},
|
||||
{
|
||||
question: "Is Roo Code really free?",
|
||||
answer: "Yes! Roo Code is completely free and open-source. You'll only pay for the AI model usage if you use a paid API (like OpenAI). If you choose free or self-hosted models, there's no cost at all.",
|
||||
},
|
||||
{
|
||||
question: "Will my code stay private?",
|
||||
answer: "Yes. Because Roo Code is an extension in your local VS Code, your code never leaves your machine unless you connect to an external AI API. Even then, you control exactly what is sent to the AI model. You can use tools like .rooignore to exclude sensitive files, and you can also run Roo Code with offline/local models for full privacy.",
|
||||
},
|
||||
{
|
||||
question: "Which AI models does Roo Code support?",
|
||||
answer: "Roo Code is model-agnostic. It works with: OpenAI models (GPT-3.5, GPT-4, etc.), Anthropic Claude, Local LLMs (through APIs or special plugins), Any other API that follows Roo Code's Model Context Protocol (MCP).",
|
||||
},
|
||||
{
|
||||
question: "Does Roo Code support my programming language?",
|
||||
answer: "Likely yes! Roo Code supports a wide range of languages—Python, Java, C#, JavaScript/TypeScript, Go, Rust, etc. Since it leverages the AI model's understanding, new or lesser-known languages may also work, depending on model support.",
|
||||
},
|
||||
{
|
||||
question: "How do I install and get started?",
|
||||
answer: "Install Roo Code from the VS Code Marketplace (or GitHub). Add your AI keys (OpenAI, Anthropic, or other) in the extension settings. Open the Roo panel (the rocket icon) in VS Code, and start typing commands in plain English!",
|
||||
},
|
||||
{
|
||||
question: "Can it handle large, enterprise-scale projects?",
|
||||
answer: "Absolutely. Roo Code uses efficient strategies (like partial-file analysis, summarization, or user-specified context) to handle large codebases. Enterprises especially appreciate the on-prem or self-hosted model option for compliance and security needs.",
|
||||
},
|
||||
{
|
||||
question: "Is it safe for enterprise use?",
|
||||
answer: "Yes. Roo Code was designed with enterprise in mind: Self-host AI models or choose your own provider. Permission gating on file writes and commands. Auditable: The entire code is open-source, so you know exactly how it operates.",
|
||||
},
|
||||
{
|
||||
question: "Can Roo Code run commands and tests automatically?",
|
||||
answer: "Yes! One of Roo Code's superpowers is command execution (optional and fully permission-based). It can: Run npm install or any terminal command you grant permission for. Execute your test suites. Open a web browser for integration tests.",
|
||||
},
|
||||
{
|
||||
question: "What if I just want a casual coding 'vibe'?",
|
||||
answer: 'Roo Code shines for both serious enterprise development and casual "vibe coding." You can ask it to quickly prototype ideas, refactor on the fly, or provide design suggestions—without a rigid, step-by-step process.',
|
||||
},
|
||||
{
|
||||
question: "Can I contribute to Roo Code?",
|
||||
answer: "Yes, please do! Roo Code is open-source on GitHub. Submit issues, suggest features, or open a pull request. There's also an active community on Discord and Reddit if you want to share feedback or help others.",
|
||||
},
|
||||
{
|
||||
question: "Where can I learn more or get help?",
|
||||
answer: "Check out: Official Documentation for setup and advanced guides. Discord & Reddit channels for community support. YouTube tutorials and blog posts from fellow developers showcasing real-world usage.",
|
||||
},
|
||||
]
|
||||
|
||||
export function FAQSection() {
|
||||
const [openIndex, setOpenIndex] = useState<number | null>(null)
|
||||
|
||||
const toggleFAQ = (index: number) => {
|
||||
setOpenIndex(openIndex === index ? null : index)
|
||||
}
|
||||
|
||||
return (
|
||||
<section id="faq-section" className="border-t border-border py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto mb-24 max-w-3xl text-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
duration: 0.6,
|
||||
ease: [0.21, 0.45, 0.27, 0.9],
|
||||
}}>
|
||||
<h2 className="bg-gradient-to-b from-foreground to-foreground/70 bg-clip-text text-4xl font-bold tracking-tight text-transparent sm:text-5xl">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-muted-foreground">
|
||||
Everything you need to know about Roo Code and how it can transform your development
|
||||
workflow.
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<div className="space-y-4">
|
||||
{faqs.map((faq, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
delay: index * 0.1,
|
||||
ease: [0.21, 0.45, 0.27, 0.9],
|
||||
}}>
|
||||
<div className="group relative rounded-lg border border-border/50 bg-background/30 backdrop-blur-xl transition-all duration-300 hover:border-border">
|
||||
<button
|
||||
onClick={() => toggleFAQ(index)}
|
||||
className="flex w-full items-center justify-between p-6 text-left"
|
||||
aria-expanded={openIndex === index}>
|
||||
<h3 className="text-lg font-medium text-foreground/90">{faq.question}</h3>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
"h-5 w-5 text-muted-foreground transition-transform duration-200",
|
||||
openIndex === index ? "rotate-180" : "",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
className={cn(
|
||||
"overflow-hidden transition-all duration-300 ease-in-out",
|
||||
openIndex === index ? "max-h-96 pb-6" : "max-h-0",
|
||||
)}>
|
||||
<div className="px-6 text-muted-foreground">
|
||||
<p>{faq.answer}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useEffect, useState, useCallback } from "react"
|
||||
import useEmblaCarousel from "embla-carousel-react"
|
||||
import Autoplay from "embla-carousel-autoplay"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||
import { features } from "@/components/homepage/features"
|
||||
|
||||
export function FeaturesMobile() {
|
||||
// configure autoplay with Embla
|
||||
const autoplayPlugin = Autoplay({
|
||||
delay: 5000,
|
||||
stopOnInteraction: true,
|
||||
stopOnMouseEnter: true,
|
||||
rootNode: (emblaRoot) => emblaRoot,
|
||||
})
|
||||
|
||||
const [emblaRef, emblaApi] = useEmblaCarousel(
|
||||
{
|
||||
loop: true,
|
||||
containScroll: "trimSnaps",
|
||||
},
|
||||
[autoplayPlugin],
|
||||
)
|
||||
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
const [scrollSnaps, setScrollSnaps] = useState<number[]>([])
|
||||
|
||||
const scrollTo = useCallback((index: number) => emblaApi && emblaApi.scrollTo(index), [emblaApi])
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const onInit = useCallback((emblaApi: any) => {
|
||||
setScrollSnaps(emblaApi.scrollSnapList())
|
||||
}, [])
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const onSelect = useCallback((emblaApi: any) => {
|
||||
setSelectedIndex(emblaApi.selectedScrollSnap())
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!emblaApi) return
|
||||
|
||||
onInit(emblaApi)
|
||||
onSelect(emblaApi)
|
||||
emblaApi.on("reInit", onInit)
|
||||
emblaApi.on("select", onSelect)
|
||||
|
||||
return () => {
|
||||
emblaApi.off("reInit", onInit)
|
||||
emblaApi.off("select", onSelect)
|
||||
}
|
||||
}, [emblaApi, onInit, onSelect])
|
||||
|
||||
return (
|
||||
<div className="md:hidden">
|
||||
<div className="relative px-4">
|
||||
<div className="overflow-hidden" ref={emblaRef}>
|
||||
<div className="flex">
|
||||
{features.map((feature, index) => (
|
||||
<div className="flex min-w-0 flex-[0_0_100%] px-4" key={index}>
|
||||
<div className="relative h-full min-h-[280px] rounded-2xl border border-border/50 bg-background/30 p-6 backdrop-blur-xl transition-colors duration-300 hover:border-border hover:bg-gray-900/20">
|
||||
<div className="mb-2 inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-blue-500/5 to-cyan-500/5 p-2.5">
|
||||
<div className="rounded-lg bg-gradient-to-r from-blue-500/80 to-cyan-500/80 p-2.5">
|
||||
<div className="text-foreground/90">{feature.icon}</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="mb-3 text-xl font-medium text-foreground/90">{feature.title}</h3>
|
||||
<p className="leading-relaxed text-muted-foreground">{feature.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation Controls */}
|
||||
<div className="mt-6 flex items-center justify-between px-4">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full border-border/50 bg-background/80 hover:bg-background"
|
||||
onClick={() => emblaApi?.scrollPrev()}>
|
||||
<ChevronLeft className="h-4 w-4 text-foreground/80" />
|
||||
<span className="sr-only">Previous slide</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full border-border/50 bg-background/80 hover:bg-background"
|
||||
onClick={() => emblaApi?.scrollNext()}>
|
||||
<ChevronRight className="h-4 w-4 text-foreground/80" />
|
||||
<span className="sr-only">Next slide</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
{scrollSnaps.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
className={`h-3 w-3 rounded-full border border-border p-0 ${index === selectedIndex ? "bg-foreground" : "bg-background"}`}
|
||||
onClick={() => scrollTo(index)}
|
||||
aria-label={`Go to slide ${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
import { FaRobot, FaCode, FaBrain, FaTools, FaTerminal, FaPuzzlePiece, FaGlobe } from "react-icons/fa"
|
||||
import { FeaturesMobile } from "./features-mobile"
|
||||
|
||||
import { ReactNode } from "react"
|
||||
|
||||
export interface Feature {
|
||||
icon: ReactNode
|
||||
title: string
|
||||
description: string
|
||||
size: "small" | "large"
|
||||
}
|
||||
|
||||
export const features: Feature[] = [
|
||||
{
|
||||
icon: <FaRobot className="h-6 w-6" />,
|
||||
title: "Your AI Dev Team in VS Code",
|
||||
description:
|
||||
"Roo Code puts a team of agentic AI assistants directly in your editor, with the power to plan, write, and fix code across multiple files.",
|
||||
size: "large",
|
||||
},
|
||||
{
|
||||
icon: <FaCode className="h-6 w-6" />,
|
||||
title: "Multiple Specialized Modes",
|
||||
description:
|
||||
"From coding to debugging to architecture, Roo Code has a mode for every dev scenario—just switch on the fly.",
|
||||
size: "small",
|
||||
},
|
||||
{
|
||||
icon: <FaBrain className="h-6 w-6" />,
|
||||
title: "Deep Project-wide Context",
|
||||
description:
|
||||
"Roo Code reads your entire codebase, preserving valid code through diff-based edits for seamless multi-file refactors.",
|
||||
size: "small",
|
||||
},
|
||||
{
|
||||
icon: <FaTools className="h-6 w-6" />,
|
||||
title: "Open-Source and Model-Agnostic",
|
||||
description:
|
||||
"Bring your own model or use local AI—no vendor lock-in. Roo Code is free, open, and adaptable to your needs.",
|
||||
size: "large",
|
||||
},
|
||||
{
|
||||
icon: <FaTerminal className="h-6 w-6" />,
|
||||
title: "Guarded Command Execution",
|
||||
description:
|
||||
"Approve or deny commands as needed. Roo Code automates your dev workflow while keeping oversight firmly in your hands.",
|
||||
size: "small",
|
||||
},
|
||||
{
|
||||
icon: <FaPuzzlePiece className="h-6 w-6" />,
|
||||
title: "Fully Customizable",
|
||||
description:
|
||||
"Create or tweak modes, define usage rules, and shape Roo Code’s behavior precisely—your code, your way.",
|
||||
size: "small",
|
||||
},
|
||||
{
|
||||
icon: <FaGlobe className="h-6 w-6" />,
|
||||
title: "Automated Browser Actions",
|
||||
description:
|
||||
"Seamlessly test and verify your web app directly from VS Code—Roo Code can open a browser, run checks, and more.",
|
||||
size: "small",
|
||||
},
|
||||
]
|
||||
|
||||
export function Features() {
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.15,
|
||||
delayChildren: 0.3,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const itemVariants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
y: 20,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: [0.21, 0.45, 0.27, 0.9],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const backgroundVariants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
duration: 1.2,
|
||||
ease: "easeOut",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="relative overflow-hidden border-t border-border py-32">
|
||||
<motion.div
|
||||
className="absolute inset-0"
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
variants={backgroundVariants}>
|
||||
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2">
|
||||
<div className="absolute left-1/2 top-1/2 h-[800px] w-full -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-blue-500/10 blur-[120px]" />
|
||||
</div>
|
||||
</motion.div>
|
||||
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto mb-24 max-w-3xl text-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
duration: 0.6,
|
||||
ease: [0.21, 0.45, 0.27, 0.9],
|
||||
}}>
|
||||
<h2 className="bg-gradient-to-b from-foreground to-foreground/70 bg-clip-text text-4xl font-bold tracking-tight text-transparent sm:text-5xl">
|
||||
Powerful features for modern developers.
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-muted-foreground">
|
||||
Everything you need to build faster and write better code.
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Carousel */}
|
||||
<FeaturesMobile />
|
||||
|
||||
{/* Desktop Grid */}
|
||||
<motion.div
|
||||
className="relative mx-auto hidden max-w-[1200px] md:block"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}>
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3 lg:gap-8">
|
||||
{features.map((feature, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
variants={itemVariants}
|
||||
className={`group relative ${feature.size === "large" ? "lg:col-span-2" : ""} ${index % 2 === 0 ? "lg:translate-y-12" : ""}`}>
|
||||
<div className="absolute -inset-px rounded-2xl bg-gradient-to-r from-blue-500/30 via-cyan-500/30 to-purple-500/30 opacity-0 blur-sm transition-opacity duration-500 group-hover:opacity-100" />
|
||||
<div className="relative h-full rounded-2xl border border-border/50 bg-background/30 p-8 backdrop-blur-xl transition-colors duration-300 hover:border-border">
|
||||
<div className="mb-5 inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-blue-500/5 to-cyan-500/5 p-2.5">
|
||||
<div className="rounded-lg bg-gradient-to-r from-blue-500/80 to-cyan-500/80 p-2.5">
|
||||
<div className="text-foreground/90">{feature.icon}</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="mb-3 text-xl font-medium text-foreground/90">{feature.title}</h3>
|
||||
<p className="leading-relaxed text-muted-foreground">{feature.description}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
export * from "./animated-background"
|
||||
export * from "./code-example"
|
||||
export * from "./company-logos"
|
||||
export * from "./faq-section"
|
||||
export * from "./features-mobile"
|
||||
export * from "./features"
|
||||
export * from "./install-section"
|
||||
export * from "./testimonials-mobile"
|
||||
export * from "./testimonials"
|
||||
export * from "./whats-new-button"
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { VscVscode } from "react-icons/vsc"
|
||||
import Link from "next/link"
|
||||
import { motion } from "framer-motion"
|
||||
|
||||
interface InstallSectionProps {
|
||||
downloads: string | null
|
||||
}
|
||||
|
||||
export function InstallSection({ downloads }: InstallSectionProps) {
|
||||
const backgroundVariants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
duration: 1.2,
|
||||
ease: "easeOut",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="relative overflow-hidden border-t border-border py-16 sm:py-24 lg:py-32">
|
||||
<motion.div
|
||||
className="absolute inset-x-0 top-1/2 -translate-y-1/2"
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
variants={backgroundVariants}>
|
||||
<div className="relative mx-auto max-w-[1200px]">
|
||||
<div className="absolute left-1/2 top-1/2 h-[500px] w-[700px] -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-blue-500/10 blur-[120px]" />
|
||||
</div>
|
||||
</motion.div>
|
||||
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-3xl text-center">
|
||||
<h2 className="text-center text-xl font-semibold uppercase tracking-wider text-muted-foreground sm:text-2xl">
|
||||
Install Roo Code — Open & Flexible
|
||||
</h2>
|
||||
<p className="mt-4 text-center text-base text-muted-foreground sm:mt-6 sm:text-lg">
|
||||
Roo Code is open-source, model-agnostic, and developer-focused. Install from the VS Code
|
||||
Marketplace or the CLI in minutes, then bring your own AI model.
|
||||
</p>
|
||||
<div className="mt-10 flex flex-col items-center justify-center gap-6">
|
||||
<Link
|
||||
href="https://marketplace.visualstudio.com/items?itemName=RooVeterinaryInc.roo-cline"
|
||||
target="_blank"
|
||||
className="group relative inline-flex w-full items-center justify-center gap-2 rounded-xl border border-border/50 bg-background/30 px-4 py-3 text-base backdrop-blur-xl transition-all duration-300 hover:border-border hover:bg-background/40 sm:w-auto sm:gap-3 sm:px-6 sm:py-4 sm:text-lg md:text-2xl">
|
||||
<div className="absolute -inset-px rounded-xl bg-gradient-to-r from-blue-500/30 via-cyan-500/30 to-purple-500/30 opacity-0 blur-sm transition-opacity duration-500 group-hover:opacity-100" />
|
||||
<div className="relative flex items-center gap-2 sm:gap-3">
|
||||
<VscVscode className="h-5 w-5 text-blue-400 sm:h-6 sm:w-6 md:h-8 md:w-8" />
|
||||
<span className="flex flex-wrap items-center gap-1 sm:gap-2 md:gap-3">
|
||||
<span className="text-foreground/90">VSCode Marketplace</span>
|
||||
{downloads !== null && (
|
||||
<>
|
||||
<span className="hidden font-black text-muted-foreground sm:inline">
|
||||
·
|
||||
</span>
|
||||
<span className="text-muted-foreground">{downloads} Downloads</span>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="group relative w-full max-w-xl">
|
||||
<div className="absolute -inset-px rounded-xl bg-gradient-to-r from-blue-500/30 via-cyan-500/30 to-purple-500/30 opacity-0 blur-sm transition-opacity duration-500 group-hover:opacity-100" />
|
||||
<div className="relative overflow-hidden rounded-xl border border-border/50 bg-background/30 backdrop-blur-xl transition-all duration-500 ease-out group-hover:border-border group-hover:bg-background/40">
|
||||
<div className="border-b border-border/50 px-3 py-2 sm:px-4">
|
||||
<div className="text-sm text-muted-foreground">Install via CLI</div>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<pre className="p-3 sm:p-4">
|
||||
<code className="whitespace-pre-wrap break-all text-sm text-foreground/90 sm:break-normal">
|
||||
code --install-extension RooVeterinaryInc.roo-cline
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import useEmblaCarousel from "embla-carousel-react"
|
||||
import AutoScroll from "embla-carousel-auto-scroll"
|
||||
import { testimonials } from "@/components/homepage/testimonials"
|
||||
|
||||
export function TestimonialsMobile() {
|
||||
const [emblaRef] = useEmblaCarousel({ loop: true }, [
|
||||
AutoScroll({
|
||||
playOnInit: true,
|
||||
speed: 1, // pixels per second - slower for smoother scrolling
|
||||
stopOnInteraction: true,
|
||||
stopOnMouseEnter: true,
|
||||
}),
|
||||
])
|
||||
|
||||
return (
|
||||
<div className="md:hidden">
|
||||
<div className="overflow-hidden px-4" ref={emblaRef}>
|
||||
<div className="flex">
|
||||
{testimonials.map((testimonial) => (
|
||||
<div className="min-w-0 flex-[0_0_100%] px-4" key={testimonial.id}>
|
||||
<div className="relative py-8">
|
||||
<svg
|
||||
className="absolute left-0 top-0 h-8 w-8 text-blue-500/30"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32">
|
||||
<path d="M9.352 4C4.456 7.456 1 13.12 1 19.36c0 5.088 3.072 8.064 6.624 8.064 3.36 0 5.856-2.688 5.856-5.856 0-3.168-2.208-5.472-5.088-5.472-.576 0-1.344.096-1.536.192.48-3.264 3.552-7.104 6.624-9.024L9.352 4zm16.512 0c-4.8 3.456-8.256 9.12-8.256 15.36 0 5.088 3.072 8.064 6.624 8.064 3.264 0 5.856-2.688 5.856-5.856 0-3.168-2.304-5.472-5.184-5.472-.576 0-1.248.096-1.44.192.48-3.264 3.456-7.104 6.528-9.024L25.864 4z" />
|
||||
</svg>
|
||||
|
||||
<blockquote className="mt-8">
|
||||
<p className="text-lg font-light italic leading-relaxed text-muted-foreground">
|
||||
"{testimonial.quote}"
|
||||
</p>
|
||||
|
||||
<footer className="mt-6">
|
||||
<div className="h-px w-12 bg-gradient-to-r from-blue-500/30 to-transparent" />
|
||||
<p className="mt-4 font-medium text-foreground/90">{testimonial.name}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{testimonial.role} at {testimonial.company}
|
||||
</p>
|
||||
</footer>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useRef } from "react"
|
||||
import { motion } from "framer-motion"
|
||||
import Image from "next/image"
|
||||
import { TestimonialsMobile } from "./testimonials-mobile"
|
||||
|
||||
export interface Testimonial {
|
||||
id: number
|
||||
name: string
|
||||
role: string
|
||||
company: string
|
||||
image?: string
|
||||
quote: string
|
||||
}
|
||||
|
||||
export const testimonials: Testimonial[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Luca",
|
||||
role: "Reviewer",
|
||||
company: "VS Code Marketplace",
|
||||
quote: "Roo Code is an absolute game-changer! 🚀 It makes coding faster, easier, and more intuitive with its smart AI-powered suggestions, real-time debugging, and automation features. The seamless integration with VS Code is a huge plus, and the constant updates ensure it keeps getting better",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Taro Woollett-Chiba",
|
||||
role: "AI Product Lead",
|
||||
company: "Vendidit",
|
||||
quote: "Easily the best AI code editor. Roo Code has the best features and capabilities, along with the best development team. I swear, they're the fastest to support new models and implement useful functionality whenever users mention it... simply amazing.",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Can Nuri",
|
||||
role: "Reviewer",
|
||||
company: "VS Code Marketplace",
|
||||
quote: "Roo Code is one of the most inspiring projects I have seen for a long time. It shapes the way I think and deal with software development.",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Michael",
|
||||
role: "Reviewer",
|
||||
company: "VS Code Marketplace",
|
||||
quote: "I switched from Windsurf to Roo Code in January and honestly, it's been a huge upgrade. Windsurf kept making mistakes and being dumb when I ask it for things. Roo just gets it. Projects that used to take a full day now wrap up before lunch. ",
|
||||
},
|
||||
]
|
||||
|
||||
export function Testimonials() {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.15,
|
||||
delayChildren: 0.3,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const itemVariants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
y: 20,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: [0.21, 0.45, 0.27, 0.9],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const backgroundVariants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
duration: 1.2,
|
||||
ease: "easeOut",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<section ref={containerRef} className="relative overflow-hidden border-t border-border py-32">
|
||||
<motion.div
|
||||
className="absolute inset-0"
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
variants={backgroundVariants}>
|
||||
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2">
|
||||
<div className="absolute left-1/2 top-1/2 h-[800px] w-full -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-blue-500/10 blur-[120px]" />
|
||||
</div>
|
||||
</motion.div>
|
||||
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto mb-24 max-w-3xl text-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
duration: 0.6,
|
||||
ease: [0.21, 0.45, 0.27, 0.9],
|
||||
}}>
|
||||
<h2 className="bg-gradient-to-b from-foreground to-foreground/70 bg-clip-text text-4xl font-bold tracking-tight text-transparent sm:text-5xl">
|
||||
Empowering developers worldwide.
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-muted-foreground">
|
||||
Join thousands of developers who are revolutionizing their workflow with AI-powered
|
||||
assistance.
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Carousel */}
|
||||
<TestimonialsMobile />
|
||||
|
||||
{/* Desktop Grid */}
|
||||
<motion.div
|
||||
className="relative mx-auto hidden max-w-[1200px] md:block"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}>
|
||||
<div className="relative grid grid-cols-1 gap-12 md:grid-cols-2">
|
||||
{testimonials.map((testimonial, index) => (
|
||||
<motion.div
|
||||
key={testimonial.id}
|
||||
variants={itemVariants}
|
||||
className={`group relative ${index % 2 === 0 ? "md:translate-y-4" : "md:translate-y-12"}`}>
|
||||
<div className="absolute -inset-px rounded-2xl bg-gradient-to-r from-blue-500/30 via-cyan-500/30 to-purple-500/30 opacity-0 blur-sm transition-all duration-500 ease-out group-hover:opacity-100" />
|
||||
<div className="relative h-full rounded-2xl border border-border/50 bg-background/30 backdrop-blur-xl transition-all duration-500 ease-out group-hover:border-border group-hover:bg-background/40">
|
||||
{testimonial.image && (
|
||||
<div className="absolute -right-3 -top-3 h-16 w-16 overflow-hidden rounded-xl border border-border/50 bg-background/50 p-1.5 backdrop-blur-xl transition-all duration-500 ease-out group-hover:scale-105">
|
||||
<div className="relative h-full w-full overflow-hidden rounded-lg">
|
||||
<Image
|
||||
src={testimonial.image || "/placeholder_pfp.png"}
|
||||
alt={testimonial.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="p-8">
|
||||
<div className="mb-6">
|
||||
<svg
|
||||
className="h-8 w-8 text-blue-500/20"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32">
|
||||
<path d="M9.352 4C4.456 7.456 1 13.12 1 19.36c0 5.088 3.072 8.064 6.624 8.064 3.36 0 5.856-2.688 5.856-5.856 0-3.168-2.208-5.472-5.088-5.472-.576 0-1.344.096-1.536.192.48-3.264 3.552-7.104 6.624-9.024L9.352 4zm16.512 0c-4.8 3.456-8.256 9.12-8.256 15.36 0 5.088 3.072 8.064 6.624 8.064 3.264 0 5.856-2.688 5.856-5.856 0-3.168-2.304-5.472-5.184-5.472-.576 0-1.248.096-1.44.192.48-3.264 3.456-7.104 6.528-9.024L25.864 4z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<p className="relative mb-6 text-lg leading-relaxed text-muted-foreground">
|
||||
{testimonial.quote}
|
||||
</p>
|
||||
|
||||
<div className="relative">
|
||||
<div className="mb-4 h-px w-12 bg-gradient-to-r from-blue-500/50 to-transparent" />
|
||||
<h3 className="font-medium text-foreground/90">{testimonial.name}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{testimonial.role} at {testimonial.company}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,229 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useState, useRef, useEffect } from "react"
|
||||
import { motion, AnimatePresence } from "framer-motion"
|
||||
import { X, ArrowRight, Code2, Users2, Zap } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
interface FeatureProps {
|
||||
icon: React.ComponentType<{ className?: string }>
|
||||
color: "blue" | "purple" | "green"
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
function Feature({ icon: Icon, color, title, description }: FeatureProps) {
|
||||
const bgColor = {
|
||||
blue: "bg-blue-500/20",
|
||||
purple: "bg-purple-500/20",
|
||||
green: "bg-green-500/20",
|
||||
}[color]
|
||||
|
||||
const textColor = {
|
||||
blue: "text-blue-400",
|
||||
purple: "text-purple-400",
|
||||
green: "text-green-400",
|
||||
}[color]
|
||||
|
||||
return (
|
||||
<div className="space-y-1.5 sm:space-y-2">
|
||||
<div className="flex items-center gap-1 space-x-2">
|
||||
<div className={`rounded-full ${bgColor} p-3 ${textColor}`}>
|
||||
<Icon className="h-6 w-6" />
|
||||
</div>
|
||||
<h3 className="text-base font-semibold sm:text-lg">{title}</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-400 sm:text-base">{description}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const version = "v3.8.0"
|
||||
|
||||
export function WhatsNewButton() {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const buttonRef = useRef<HTMLDivElement>(null)
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
|
||||
// animated border effect
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current
|
||||
const button = buttonRef.current
|
||||
|
||||
if (!canvas || !button) return
|
||||
|
||||
const ctx = canvas.getContext("2d")
|
||||
if (!ctx) return
|
||||
|
||||
// set canvas size to match button size with extra space for glow
|
||||
const updateCanvasSize = () => {
|
||||
const rect = button.getBoundingClientRect()
|
||||
// add extra padding for the glow effect
|
||||
canvas.width = rect.width + 8
|
||||
canvas.height = rect.height + 8
|
||||
|
||||
// position the canvas precisely
|
||||
canvas.style.width = `${canvas.width}px`
|
||||
canvas.style.height = `${canvas.height}px`
|
||||
}
|
||||
|
||||
updateCanvasSize()
|
||||
window.addEventListener("resize", updateCanvasSize)
|
||||
|
||||
// animation variables
|
||||
let animationId: number
|
||||
let position = 0
|
||||
|
||||
const animate = () => {
|
||||
if (!ctx || !canvas) return
|
||||
|
||||
// clear canvas
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// calculate border path
|
||||
const width = canvas.width - 4
|
||||
const height = canvas.height - 4
|
||||
const x = 2
|
||||
const y = 2
|
||||
const radius = height / 2
|
||||
|
||||
// draw rounded rectangle path
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x + radius, y)
|
||||
ctx.lineTo(x + width - radius, y)
|
||||
ctx.arcTo(x + width, y, x + width, y + radius, radius)
|
||||
ctx.lineTo(x + width, y + height - radius)
|
||||
ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius)
|
||||
ctx.lineTo(x + radius, y + height)
|
||||
ctx.arcTo(x, y + height, x, y + height - radius, radius)
|
||||
ctx.lineTo(x, y + radius)
|
||||
ctx.arcTo(x, y, x + radius, y, radius)
|
||||
ctx.closePath()
|
||||
|
||||
// create rotating gradient effect
|
||||
position = (position + 0.016) % (Math.PI * 2)
|
||||
|
||||
const centerX = canvas.width / 2
|
||||
const centerY = canvas.height / 2
|
||||
const blueColor = "70, 130, 255"
|
||||
|
||||
// create rotating gradient
|
||||
const gradient = ctx.createConicGradient(position, centerX, centerY)
|
||||
|
||||
// add color stops for a single flowing stream
|
||||
gradient.addColorStop(0, `rgba(${blueColor}, 0)`)
|
||||
gradient.addColorStop(0.2, `rgba(${blueColor}, 0.8)`)
|
||||
gradient.addColorStop(0.4, `rgba(${blueColor}, 0)`)
|
||||
gradient.addColorStop(1, `rgba(${blueColor}, 0)`)
|
||||
|
||||
// apply gradient
|
||||
ctx.strokeStyle = gradient
|
||||
ctx.lineWidth = 1.5
|
||||
ctx.stroke()
|
||||
|
||||
// add subtle glow effect
|
||||
ctx.shadowColor = `rgba(${blueColor}, 0.6)`
|
||||
ctx.shadowBlur = 5
|
||||
ctx.strokeStyle = `rgba(${blueColor}, 0.3)`
|
||||
ctx.lineWidth = 0.5
|
||||
ctx.stroke()
|
||||
|
||||
animationId = requestAnimationFrame(animate)
|
||||
}
|
||||
|
||||
animate()
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", updateCanvasSize)
|
||||
if (animationId) cancelAnimationFrame(animationId)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative inline-flex" ref={buttonRef}>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"
|
||||
style={{ pointerEvents: "none" }}
|
||||
/>
|
||||
<Link
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
setIsOpen(true)
|
||||
}}
|
||||
className="relative z-10 flex items-center space-x-2 rounded-full bg-black px-4 py-2 text-sm font-medium text-white transition-all hover:bg-gray-900">
|
||||
<span>See what's new in {version}</span>
|
||||
<ArrowRight className="h-3.5 w-3.5" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<>
|
||||
<motion.div
|
||||
className="fixed inset-0 z-40 bg-black/80 backdrop-blur-sm"
|
||||
initial={{ opacity: 0, backdropFilter: "blur(0px)" }}
|
||||
animate={{ opacity: 1, backdropFilter: "blur(8px)" }}
|
||||
exit={{ opacity: 0, backdropFilter: "blur(0px)" }}
|
||||
transition={{ duration: 0.2 }}
|
||||
/>
|
||||
<div className="fixed inset-0 z-50 overflow-y-auto" onClick={() => setIsOpen(false)}>
|
||||
<div className="flex min-h-full items-center justify-center p-4">
|
||||
<motion.div
|
||||
className="relative w-full max-w-2xl rounded-lg border border-gray-800 bg-black p-6 sm:p-8"
|
||||
initial={{ opacity: 0, y: 20, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 20, scale: 0.95 }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
damping: 20,
|
||||
stiffness: 400,
|
||||
mass: 0.6,
|
||||
duration: 0.25,
|
||||
}}
|
||||
onClick={(e) => {
|
||||
// prevent clicks inside the panel from closing it
|
||||
e.stopPropagation()
|
||||
}}>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<h2 className="text-xl font-bold sm:text-2xl">
|
||||
What's New in Roo Code {version}
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="flex-shrink-0 rounded-full p-1.5 text-gray-400 hover:bg-gray-800 hover:text-white">
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 space-y-4 sm:mt-6 sm:space-y-6">
|
||||
<Feature
|
||||
icon={Code2}
|
||||
color="blue"
|
||||
title="AI-Powered Code Generation"
|
||||
description="Generate high-quality code snippets and entire components with our new AI assistant. Trained on millions of code repositories to understand your project context."
|
||||
/>
|
||||
<Feature
|
||||
icon={Users2}
|
||||
color="purple"
|
||||
title="Real-time Collaboration"
|
||||
description="Work together with your team in real-time with our new collaborative editing features. See changes as they happen and resolve conflicts automatically."
|
||||
/>
|
||||
<Feature
|
||||
icon={Zap}
|
||||
color="green"
|
||||
title="Performance Optimizations"
|
||||
description="We've completely rewritten our core engine for blazing fast performance. Experience up to 10x faster build times and smoother development workflow."
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { Providers } from "./providers"
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { usePathname, useSearchParams } from "next/navigation"
|
||||
import posthog from "posthog-js"
|
||||
import { PostHogProvider as OriginalPostHogProvider } from "posthog-js/react"
|
||||
import { useEffect, Suspense } from "react"
|
||||
|
||||
// Create a separate component for analytics tracking that uses useSearchParams
|
||||
function PageViewTracker() {
|
||||
const pathname = usePathname()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
// Track page views
|
||||
useEffect(() => {
|
||||
// Only track page views if PostHog is properly initialized
|
||||
if (pathname && process.env.NEXT_PUBLIC_POSTHOG_KEY) {
|
||||
let url = window.location.origin + pathname
|
||||
if (searchParams && searchParams.toString()) {
|
||||
url = url + `?${searchParams.toString()}`
|
||||
}
|
||||
posthog.capture("$pageview", {
|
||||
$current_url: url,
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathname, searchParams.toString()])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function PostHogProvider({ children }: { children: React.ReactNode }) {
|
||||
useEffect(() => {
|
||||
// Initialize PostHog only on the client side
|
||||
if (typeof window !== "undefined") {
|
||||
const posthogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY
|
||||
const posthogHost = process.env.NEXT_PUBLIC_POSTHOG_HOST
|
||||
|
||||
// Check if environment variables are set
|
||||
if (!posthogKey) {
|
||||
console.warn(
|
||||
"PostHog API key is missing. Analytics will be disabled. " +
|
||||
"Please set NEXT_PUBLIC_POSTHOG_KEY in your .env file.",
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (!posthogHost) {
|
||||
console.warn(
|
||||
"PostHog host URL is missing. Using default host. " +
|
||||
"Please set NEXT_PUBLIC_POSTHOG_HOST in your .env file.",
|
||||
)
|
||||
}
|
||||
|
||||
posthog.init(posthogKey, {
|
||||
api_host: posthogHost || "https://us.i.posthog.com",
|
||||
capture_pageview: false, // We'll handle this manually
|
||||
loaded: (posthogInstance) => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
// Log to console in development
|
||||
posthogInstance.debug()
|
||||
}
|
||||
},
|
||||
respect_dnt: true, // Respect Do Not Track
|
||||
})
|
||||
}
|
||||
|
||||
// No explicit cleanup needed for posthog-js v1.231.0
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<OriginalPostHogProvider client={posthog}>
|
||||
<Suspense fallback={null}>
|
||||
<PageViewTracker />
|
||||
</Suspense>
|
||||
{children}
|
||||
</OriginalPostHogProvider>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
||||
import { ThemeProvider } from "next-themes"
|
||||
|
||||
import { PostHogProvider } from "./posthog-provider"
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
export const Providers = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<PostHogProvider>
|
||||
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem={false}>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</PostHogProvider>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||
outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2",
|
||||
sm: "h-8 rounded-md px-3 text-xs",
|
||||
lg: "h-10 rounded-md px-8",
|
||||
icon: "h-9 w-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
|
||||
},
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
|
|
@ -1,311 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as RechartsPrimitive from "recharts"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||
const THEMES = { light: "", dark: ".dark" } as const
|
||||
|
||||
export type ChartConfig = {
|
||||
[k in string]: {
|
||||
label?: React.ReactNode
|
||||
icon?: React.ComponentType
|
||||
} & ({ color?: string; theme?: never } | { color?: never; theme: Record<keyof typeof THEMES, string> })
|
||||
}
|
||||
|
||||
type ChartContextProps = {
|
||||
config: ChartConfig
|
||||
}
|
||||
|
||||
const ChartContext = React.createContext<ChartContextProps | null>(null)
|
||||
|
||||
function useChart() {
|
||||
const context = React.useContext(ChartContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useChart must be used within a <ChartContainer />")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
const ChartContainer = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<"div"> & {
|
||||
config: ChartConfig
|
||||
children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>["children"]
|
||||
}
|
||||
>(({ id, className, children, config, ...props }, ref) => {
|
||||
const uniqueId = React.useId()
|
||||
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
|
||||
|
||||
return (
|
||||
<ChartContext.Provider value={{ config }}>
|
||||
<div
|
||||
data-chart={chartId}
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
|
||||
className,
|
||||
)}
|
||||
{...props}>
|
||||
<ChartStyle id={chartId} config={config} />
|
||||
<RechartsPrimitive.ResponsiveContainer>{children}</RechartsPrimitive.ResponsiveContainer>
|
||||
</div>
|
||||
</ChartContext.Provider>
|
||||
)
|
||||
})
|
||||
ChartContainer.displayName = "Chart"
|
||||
|
||||
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
||||
const colorConfig = Object.entries(config).filter(([, config]) => config.theme || config.color)
|
||||
|
||||
if (!colorConfig.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: Object.entries(THEMES)
|
||||
.map(
|
||||
([theme, prefix]) => `
|
||||
${prefix} [data-chart=${id}] {
|
||||
${colorConfig
|
||||
.map(([key, itemConfig]) => {
|
||||
const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || itemConfig.color
|
||||
return color ? ` --color-${key}: ${color};` : null
|
||||
})
|
||||
.join("\n")}
|
||||
}
|
||||
`,
|
||||
)
|
||||
.join("\n"),
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const ChartTooltip = RechartsPrimitive.Tooltip
|
||||
|
||||
const ChartTooltipContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
||||
React.ComponentProps<"div"> & {
|
||||
hideLabel?: boolean
|
||||
hideIndicator?: boolean
|
||||
indicator?: "line" | "dot" | "dashed"
|
||||
nameKey?: string
|
||||
labelKey?: string
|
||||
}
|
||||
>(
|
||||
(
|
||||
{
|
||||
active,
|
||||
payload,
|
||||
className,
|
||||
indicator = "dot",
|
||||
hideLabel = false,
|
||||
hideIndicator = false,
|
||||
label,
|
||||
labelFormatter,
|
||||
labelClassName,
|
||||
formatter,
|
||||
color,
|
||||
nameKey,
|
||||
labelKey,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { config } = useChart()
|
||||
|
||||
const tooltipLabel = React.useMemo(() => {
|
||||
if (hideLabel || !payload?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const [item] = payload
|
||||
const key = `${labelKey || item?.dataKey || item?.name || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const value =
|
||||
!labelKey && typeof label === "string"
|
||||
? config[label as keyof typeof config]?.label || label
|
||||
: itemConfig?.label
|
||||
|
||||
if (labelFormatter) {
|
||||
return <div className={cn("font-medium", labelClassName)}>{labelFormatter(value, payload)}</div>
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div className={cn("font-medium", labelClassName)}>{value}</div>
|
||||
}, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey])
|
||||
|
||||
if (!active || !payload?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const nestLabel = payload.length === 1 && indicator !== "dot"
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
|
||||
className,
|
||||
)}>
|
||||
{!nestLabel ? tooltipLabel : null}
|
||||
<div className="grid gap-1.5">
|
||||
{payload.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const indicatorColor = color || item.payload.fill || item.color
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.dataKey}
|
||||
className={cn(
|
||||
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
||||
indicator === "dot" && "items-center",
|
||||
)}>
|
||||
{formatter && item?.value !== undefined && item.name ? (
|
||||
formatter(item.value, item.name, item, index, item.payload)
|
||||
) : (
|
||||
<>
|
||||
{itemConfig?.icon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
!hideIndicator && (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||
indicator === "dashed",
|
||||
"my-0.5": nestLabel && indicator === "dashed",
|
||||
},
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--color-bg": indicatorColor,
|
||||
"--color-border": indicatorColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 justify-between leading-none",
|
||||
nestLabel ? "items-end" : "items-center",
|
||||
)}>
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-muted-foreground">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
</div>
|
||||
{item.value && (
|
||||
<span className="font-mono font-medium tabular-nums text-foreground">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
ChartTooltipContent.displayName = "ChartTooltip"
|
||||
|
||||
const ChartLegend = RechartsPrimitive.Legend
|
||||
|
||||
const ChartLegendContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<"div"> &
|
||||
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
||||
hideIcon?: boolean
|
||||
nameKey?: string
|
||||
}
|
||||
>(({ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey }, ref) => {
|
||||
const { config } = useChart()
|
||||
|
||||
if (!payload?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-4",
|
||||
verticalAlign === "top" ? "pb-3" : "pt-3",
|
||||
className,
|
||||
)}>
|
||||
{payload.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground",
|
||||
)}>
|
||||
{itemConfig?.icon && !hideIcon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
<div
|
||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{itemConfig?.label}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
ChartLegendContent.displayName = "ChartLegend"
|
||||
|
||||
// Helper to extract item config from a payload.
|
||||
function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {
|
||||
if (typeof payload !== "object" || payload === null) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const payloadPayload =
|
||||
"payload" in payload && typeof payload.payload === "object" && payload.payload !== null
|
||||
? payload.payload
|
||||
: undefined
|
||||
|
||||
let configLabelKey: string = key
|
||||
|
||||
if (key in payload && typeof payload[key as keyof typeof payload] === "string") {
|
||||
configLabelKey = payload[key as keyof typeof payload] as string
|
||||
} else if (
|
||||
payloadPayload &&
|
||||
key in payloadPayload &&
|
||||
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
||||
) {
|
||||
configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string
|
||||
}
|
||||
|
||||
return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config]
|
||||
}
|
||||
|
||||
export { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartStyle }
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
export * from "./button"
|
||||
export * from "./chart"
|
||||
export * from "./modal"
|
||||
export * from "./scroll-button"
|
||||
export * from "./table"
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { X } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Dialog = DialogPrimitive.Root
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger
|
||||
|
||||
const DialogPortal = DialogPrimitive.Portal
|
||||
|
||||
const DialogClose = DialogPrimitive.Close
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
))
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
|
||||
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
|
||||
)
|
||||
DialogHeader.displayName = "DialogHeader"
|
||||
|
||||
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
|
||||
)
|
||||
DialogFooter.displayName = "DialogFooter"
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
|
||||
))
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogTrigger,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { usePathname, useRouter } from "next/navigation"
|
||||
|
||||
interface ScrollButtonProps {
|
||||
targetId: string
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
export function ScrollButton({ targetId, children, className = "", onClick }: ScrollButtonProps) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
|
||||
const handleClick = () => {
|
||||
if (pathname === "/") {
|
||||
// if we're on the home page, use smooth scrolling
|
||||
const section = document.getElementById(targetId)
|
||||
section?.scrollIntoView({ behavior: "smooth" })
|
||||
} else {
|
||||
// if we're on a different page, navigate directly to the section
|
||||
router.push(`/#${targetId}`)
|
||||
}
|
||||
onClick?.()
|
||||
}
|
||||
|
||||
return (
|
||||
<button onClick={handleClick} className={className}>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<table ref={ref} className={cn("w-full caption-bottom text-sm", className)} {...props} />
|
||||
</div>
|
||||
),
|
||||
)
|
||||
Table.displayName = "Table"
|
||||
|
||||
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
||||
({ className, ...props }, ref) => <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />,
|
||||
)
|
||||
TableHeader.displayName = "TableHeader"
|
||||
|
||||
const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
|
||||
),
|
||||
)
|
||||
TableBody.displayName = "TableBody"
|
||||
|
||||
const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
)
|
||||
TableFooter.displayName = "TableFooter"
|
||||
|
||||
const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
)
|
||||
TableRow.displayName = "TableRow"
|
||||
|
||||
const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(
|
||||
// eslint-disable-next-line react/prop-types
|
||||
({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
)
|
||||
TableHead.displayName = "TableHead"
|
||||
|
||||
const TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(
|
||||
// eslint-disable-next-line react/prop-types
|
||||
({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
)
|
||||
TableCell.displayName = "TableCell"
|
||||
|
||||
const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<caption ref={ref} className={cn("mt-4 text-sm text-muted-foreground", className)} {...props} />
|
||||
),
|
||||
)
|
||||
TableCaption.displayName = "TableCaption"
|
||||
|
||||
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import { drizzle } from "drizzle-orm/libsql"
|
||||
|
||||
import { schema } from "./schema"
|
||||
|
||||
if ((!process.env.TURSO_CONNECTION_URL || !process.env.TURSO_AUTH_TOKEN) && !process.env.BENCHMARKS_DB_PATH) {
|
||||
throw new Error("TURSO_CONNECTION_URL and TURSO_AUTH_TOKEN or BENCHMARKS_DB_PATH must be set")
|
||||
}
|
||||
|
||||
const connection = process.env.BENCHMARKS_DB_PATH
|
||||
? { url: process.env.BENCHMARKS_DB_PATH, concurrency: 50 }
|
||||
: { url: process.env.TURSO_CONNECTION_URL!, authToken: process.env.TURSO_AUTH_TOKEN! }
|
||||
|
||||
export const db = drizzle({ schema, connection })
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
export { db } from "./db"
|
||||
export * from "./schema"
|
||||
|
||||
export * from "./queries/runs"
|
||||
export * from "./queries/tasks"
|
||||
export * from "./queries/taskMetrics"
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export class RecordNotFoundError extends Error {}
|
||||
|
||||
export class RecordNotCreatedError extends Error {}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import { desc, eq } from "drizzle-orm"
|
||||
|
||||
import { RecordNotFoundError } from "./errors"
|
||||
import { schema } from "../schema"
|
||||
import { db } from "../db"
|
||||
|
||||
const table = schema.runs
|
||||
|
||||
export const findRun = async (id: number) => {
|
||||
const run = await db.query.runs.findFirst({ where: eq(table.id, id) })
|
||||
|
||||
if (!run) {
|
||||
throw new RecordNotFoundError()
|
||||
}
|
||||
|
||||
return run
|
||||
}
|
||||
|
||||
export const getRuns = async () => db.query.runs.findMany({ orderBy: desc(table.id), with: { taskMetrics: true } })
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import { eq } from "drizzle-orm"
|
||||
|
||||
import { RecordNotFoundError } from "./errors"
|
||||
import { taskMetrics } from "../schema"
|
||||
import { db } from "../db"
|
||||
|
||||
const table = taskMetrics
|
||||
|
||||
export const findTaskMetrics = async (id: number) => {
|
||||
const run = await db.query.taskMetrics.findFirst({ where: eq(table.id, id) })
|
||||
|
||||
if (!run) {
|
||||
throw new RecordNotFoundError()
|
||||
}
|
||||
|
||||
return run
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import { and, eq } from "drizzle-orm"
|
||||
|
||||
import { RecordNotFoundError } from "./errors"
|
||||
import { tasks } from "../schema"
|
||||
import { db } from "../db"
|
||||
|
||||
export const findTask = async (id: number) => {
|
||||
const run = await db.query.tasks.findFirst({ where: eq(tasks.id, id) })
|
||||
|
||||
if (!run) {
|
||||
throw new RecordNotFoundError()
|
||||
}
|
||||
|
||||
return run
|
||||
}
|
||||
|
||||
type GetTask = {
|
||||
runId: number
|
||||
language: string
|
||||
exercise: string
|
||||
}
|
||||
|
||||
export const getTask = async ({ runId, language, exercise }: GetTask) =>
|
||||
db.query.tasks.findFirst({
|
||||
where: and(eq(tasks.runId, runId), eq(tasks.language, language), eq(tasks.exercise, exercise)),
|
||||
})
|
||||
|
||||
export const getTasks = async (runId: number) =>
|
||||
db.query.tasks.findMany({ where: eq(tasks.runId, runId), with: { taskMetrics: true } })
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
import { sqliteTable, text, real, integer, blob, uniqueIndex } from "drizzle-orm/sqlite-core"
|
||||
import { relations } from "drizzle-orm"
|
||||
|
||||
/**
|
||||
* runs
|
||||
*/
|
||||
|
||||
export const runs = sqliteTable("runs", {
|
||||
id: integer({ mode: "number" }).primaryKey({ autoIncrement: true }),
|
||||
taskMetricsId: integer({ mode: "number" }).references(() => taskMetrics.id),
|
||||
model: text().notNull(),
|
||||
description: text(),
|
||||
settings: blob({ mode: "json" }).$type<unknown>(),
|
||||
pid: integer({ mode: "number" }),
|
||||
socketPath: text().notNull(),
|
||||
passed: integer({ mode: "number" }).default(0).notNull(),
|
||||
failed: integer({ mode: "number" }).default(0).notNull(),
|
||||
createdAt: integer({ mode: "timestamp" }).notNull(),
|
||||
})
|
||||
|
||||
export const runsRelations = relations(runs, ({ one }) => ({
|
||||
taskMetrics: one(taskMetrics, { fields: [runs.taskMetricsId], references: [taskMetrics.id] }),
|
||||
}))
|
||||
|
||||
export type Run = typeof runs.$inferSelect
|
||||
|
||||
/**
|
||||
* tasks
|
||||
*/
|
||||
|
||||
export const tasks = sqliteTable(
|
||||
"tasks",
|
||||
{
|
||||
id: integer({ mode: "number" }).primaryKey({ autoIncrement: true }),
|
||||
runId: integer({ mode: "number" })
|
||||
.references(() => runs.id)
|
||||
.notNull(),
|
||||
taskMetricsId: integer({ mode: "number" }).references(() => taskMetrics.id),
|
||||
language: text().notNull(),
|
||||
exercise: text().notNull(),
|
||||
passed: integer({ mode: "boolean" }),
|
||||
startedAt: integer({ mode: "timestamp" }),
|
||||
finishedAt: integer({ mode: "timestamp" }),
|
||||
createdAt: integer({ mode: "timestamp" }).notNull(),
|
||||
},
|
||||
(table) => [uniqueIndex("tasks_language_exercise_idx").on(table.runId, table.language, table.exercise)],
|
||||
)
|
||||
|
||||
export const tasksRelations = relations(tasks, ({ one }) => ({
|
||||
run: one(runs, { fields: [tasks.runId], references: [runs.id] }),
|
||||
taskMetrics: one(taskMetrics, { fields: [tasks.taskMetricsId], references: [taskMetrics.id] }),
|
||||
}))
|
||||
|
||||
export type Task = typeof tasks.$inferSelect
|
||||
|
||||
/**
|
||||
* taskMetrics
|
||||
*/
|
||||
|
||||
export const taskMetrics = sqliteTable("taskMetrics", {
|
||||
id: integer({ mode: "number" }).primaryKey({ autoIncrement: true }),
|
||||
tokensIn: integer({ mode: "number" }).notNull(),
|
||||
tokensOut: integer({ mode: "number" }).notNull(),
|
||||
tokensContext: integer({ mode: "number" }).notNull(),
|
||||
cacheWrites: integer({ mode: "number" }).notNull(),
|
||||
cacheReads: integer({ mode: "number" }).notNull(),
|
||||
cost: real().notNull(),
|
||||
duration: integer({ mode: "number" }).notNull(),
|
||||
createdAt: integer({ mode: "timestamp" }).notNull(),
|
||||
})
|
||||
|
||||
export type TaskMetrics = typeof taskMetrics.$inferSelect
|
||||
|
||||
/**
|
||||
* schema
|
||||
*/
|
||||
|
||||
export const schema = { runs, runsRelations, tasks, tasksRelations, taskMetrics }
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
CREATE TABLE `runs` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`taskMetricsId` integer,
|
||||
`model` text NOT NULL,
|
||||
`description` text,
|
||||
`pid` integer,
|
||||
`socketPath` text NOT NULL,
|
||||
`passed` integer DEFAULT 0 NOT NULL,
|
||||
`failed` integer DEFAULT 0 NOT NULL,
|
||||
`createdAt` integer NOT NULL,
|
||||
FOREIGN KEY (`taskMetricsId`) REFERENCES `taskMetrics`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `taskMetrics` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`tokensIn` integer NOT NULL,
|
||||
`tokensOut` integer NOT NULL,
|
||||
`tokensContext` integer NOT NULL,
|
||||
`cacheWrites` integer NOT NULL,
|
||||
`cacheReads` integer NOT NULL,
|
||||
`cost` real NOT NULL,
|
||||
`duration` integer NOT NULL,
|
||||
`createdAt` integer NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `tasks` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`runId` integer NOT NULL,
|
||||
`taskMetricsId` integer,
|
||||
`language` text NOT NULL,
|
||||
`exercise` text NOT NULL,
|
||||
`passed` integer,
|
||||
`startedAt` integer,
|
||||
`finishedAt` integer,
|
||||
`createdAt` integer NOT NULL,
|
||||
FOREIGN KEY (`runId`) REFERENCES `runs`(`id`) ON UPDATE no action ON DELETE no action,
|
||||
FOREIGN KEY (`taskMetricsId`) REFERENCES `taskMetrics`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `tasks_language_exercise_idx` ON `tasks` (`runId`,`language`,`exercise`);
|
||||
|
|
@ -1 +0,0 @@
|
|||
ALTER TABLE `runs` ADD `settings` blob;
|
||||
|
|
@ -1,274 +0,0 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "c0fa8491-b5c0-493d-aa32-ddf280259c30",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"runs": {
|
||||
"name": "runs",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"taskMetricsId": {
|
||||
"name": "taskMetricsId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"model": {
|
||||
"name": "model",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"pid": {
|
||||
"name": "pid",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"socketPath": {
|
||||
"name": "socketPath",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"passed": {
|
||||
"name": "passed",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"failed": {
|
||||
"name": "failed",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"runs_taskMetricsId_taskMetrics_id_fk": {
|
||||
"name": "runs_taskMetricsId_taskMetrics_id_fk",
|
||||
"tableFrom": "runs",
|
||||
"tableTo": "taskMetrics",
|
||||
"columnsFrom": ["taskMetricsId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"taskMetrics": {
|
||||
"name": "taskMetrics",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"tokensIn": {
|
||||
"name": "tokensIn",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"tokensOut": {
|
||||
"name": "tokensOut",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"tokensContext": {
|
||||
"name": "tokensContext",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"cacheWrites": {
|
||||
"name": "cacheWrites",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"cacheReads": {
|
||||
"name": "cacheReads",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"cost": {
|
||||
"name": "cost",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"duration": {
|
||||
"name": "duration",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"tasks": {
|
||||
"name": "tasks",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"runId": {
|
||||
"name": "runId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"taskMetricsId": {
|
||||
"name": "taskMetricsId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"language": {
|
||||
"name": "language",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"exercise": {
|
||||
"name": "exercise",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"passed": {
|
||||
"name": "passed",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"startedAt": {
|
||||
"name": "startedAt",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"finishedAt": {
|
||||
"name": "finishedAt",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"tasks_language_exercise_idx": {
|
||||
"name": "tasks_language_exercise_idx",
|
||||
"columns": ["runId", "language", "exercise"],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"tasks_runId_runs_id_fk": {
|
||||
"name": "tasks_runId_runs_id_fk",
|
||||
"tableFrom": "tasks",
|
||||
"tableTo": "runs",
|
||||
"columnsFrom": ["runId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"tasks_taskMetricsId_taskMetrics_id_fk": {
|
||||
"name": "tasks_taskMetricsId_taskMetrics_id_fk",
|
||||
"tableFrom": "tasks",
|
||||
"tableTo": "taskMetrics",
|
||||
"columnsFrom": ["taskMetricsId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,281 +0,0 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "8906647f-81d6-498a-897c-b1638c04c69a",
|
||||
"prevId": "c0fa8491-b5c0-493d-aa32-ddf280259c30",
|
||||
"tables": {
|
||||
"runs": {
|
||||
"name": "runs",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"taskMetricsId": {
|
||||
"name": "taskMetricsId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"model": {
|
||||
"name": "model",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"settings": {
|
||||
"name": "settings",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"pid": {
|
||||
"name": "pid",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"socketPath": {
|
||||
"name": "socketPath",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"passed": {
|
||||
"name": "passed",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"failed": {
|
||||
"name": "failed",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"runs_taskMetricsId_taskMetrics_id_fk": {
|
||||
"name": "runs_taskMetricsId_taskMetrics_id_fk",
|
||||
"tableFrom": "runs",
|
||||
"tableTo": "taskMetrics",
|
||||
"columnsFrom": ["taskMetricsId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"taskMetrics": {
|
||||
"name": "taskMetrics",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"tokensIn": {
|
||||
"name": "tokensIn",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"tokensOut": {
|
||||
"name": "tokensOut",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"tokensContext": {
|
||||
"name": "tokensContext",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"cacheWrites": {
|
||||
"name": "cacheWrites",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"cacheReads": {
|
||||
"name": "cacheReads",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"cost": {
|
||||
"name": "cost",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"duration": {
|
||||
"name": "duration",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"tasks": {
|
||||
"name": "tasks",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"runId": {
|
||||
"name": "runId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"taskMetricsId": {
|
||||
"name": "taskMetricsId",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"language": {
|
||||
"name": "language",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"exercise": {
|
||||
"name": "exercise",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"passed": {
|
||||
"name": "passed",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"startedAt": {
|
||||
"name": "startedAt",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"finishedAt": {
|
||||
"name": "finishedAt",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"createdAt": {
|
||||
"name": "createdAt",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"tasks_language_exercise_idx": {
|
||||
"name": "tasks_language_exercise_idx",
|
||||
"columns": ["runId", "language", "exercise"],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"tasks_runId_runs_id_fk": {
|
||||
"name": "tasks_runId_runs_id_fk",
|
||||
"tableFrom": "tasks",
|
||||
"tableTo": "runs",
|
||||
"columnsFrom": ["runId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"tasks_taskMetricsId_taskMetrics_id_fk": {
|
||||
"name": "tasks_taskMetricsId_taskMetrics_id_fk",
|
||||
"tableFrom": "tasks",
|
||||
"tableTo": "taskMetrics",
|
||||
"columnsFrom": ["taskMetricsId"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "6",
|
||||
"when": 1742599919625,
|
||||
"tag": "0000_elite_raza",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "6",
|
||||
"when": 1743089501047,
|
||||
"tag": "0001_lush_reavers",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
export const EXTERNAL_LINKS = {
|
||||
GITHUB: "https://github.com/RooVetGit/Roo-Code",
|
||||
DISCORD: "https://discord.gg/roocode",
|
||||
REDDIT: "https://reddit.com/r/RooCode",
|
||||
DOCUMENTATION: "https://docs.roocode.com",
|
||||
CAREERS: "https://careers.roocode.com",
|
||||
ISSUES: "https://github.com/RooVetGit/Roo-Code/issues",
|
||||
FEATURE_REQUESTS: "https://github.com/RooVetGit/Roo-Code/discussions/categories/feature-requests",
|
||||
COMMUNITY: "https://github.com/RooVetGit/Roo-Code/discussions",
|
||||
CHANGELOG: "https://github.com/RooVetGit/Roo-Code/blob/main/CHANGELOG.md",
|
||||
PRIVACY_POLICY_EXTENSION: "https://github.com/RooVetGit/Roo-Code/blob/main/PRIVACY.md",
|
||||
INTEGRATIONS: "https://docs.roocode.com/community",
|
||||
TUTORIALS: "https://docs.roocode.com/tutorial-videos",
|
||||
MARKETPLACE: "https://marketplace.visualstudio.com/items?itemName=RooVeterinaryInc.roo-cline",
|
||||
}
|
||||
|
||||
export const INTERNAL_LINKS = {
|
||||
PRIVACY_POLICY_WEBSITE: "/privacy",
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
const formatter = new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
})
|
||||
|
||||
export const formatCurrency = (amount: number) => formatter.format(amount)
|
||||
|
||||
export const parsePrice = (price?: string) => (price ? parseFloat(price) * 1_000_000 : undefined)
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
export const formatDuration = (durationMs: number) => {
|
||||
const seconds = Math.floor(durationMs / 1000)
|
||||
const hours = Math.floor(seconds / 3600)
|
||||
const minutes = Math.floor((seconds % 3600) / 60)
|
||||
const remainingSeconds = seconds % 60
|
||||
|
||||
const parts = []
|
||||
|
||||
if (hours > 0) {
|
||||
parts.push(`${hours}h`)
|
||||
}
|
||||
|
||||
if (minutes > 0) {
|
||||
parts.push(`${minutes}m`)
|
||||
}
|
||||
|
||||
if (remainingSeconds > 0 || parts.length === 0) {
|
||||
parts.push(`${remainingSeconds}s`)
|
||||
}
|
||||
|
||||
return parts.join(" ")
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export const formatScore = (score: number) => Math.round(score * 100)
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
export const formatTokens = (tokens: number, decimals = 0) => {
|
||||
if (tokens < 1000) {
|
||||
return tokens.toString()
|
||||
}
|
||||
|
||||
if (tokens < 1000000) {
|
||||
return `${(tokens / 1000).toFixed(decimals)}K`
|
||||
}
|
||||
|
||||
if (tokens < 1000000000) {
|
||||
return `${(tokens / 1000000).toFixed(decimals)}M`
|
||||
}
|
||||
|
||||
return `${(tokens / 1000000000).toFixed(decimals)}B`
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
export function useLogoSrc(): string {
|
||||
const { resolvedTheme } = useTheme()
|
||||
return resolvedTheme === "light" ? "/Roo-Code-Logo-Horiz-blk.svg" : "/Roo-Code-Logo-Horiz-white.svg"
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import { z } from "zod"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { ModelInfo } from "@roo-code/types"
|
||||
|
||||
const parsePrice = (price?: string) => (price ? parseFloat(price) * 1_000_000 : undefined)
|
||||
|
||||
export const openRouterModelSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
created: z.number(),
|
||||
context_length: z.number(),
|
||||
pricing: z.object({
|
||||
prompt: z.string().optional(),
|
||||
completion: z.string().optional(),
|
||||
}),
|
||||
top_provider: z
|
||||
.object({
|
||||
max_completion_tokens: z.number().nullish(),
|
||||
})
|
||||
.optional(),
|
||||
architecture: z
|
||||
.object({
|
||||
modality: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
||||
export type OpenRouterModel = z.infer<typeof openRouterModelSchema>
|
||||
|
||||
export type OpenRouterModelRecord = Record<string, OpenRouterModel & { modelInfo: ModelInfo }>
|
||||
|
||||
export const getOpenRouterModels = async (): Promise<OpenRouterModelRecord> => {
|
||||
const response = await fetch("https://openrouter.ai/api/v1/models")
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("Failed to fetch OpenRouter models")
|
||||
return {}
|
||||
}
|
||||
|
||||
const result = z.object({ data: z.array(openRouterModelSchema) }).safeParse(await response.json())
|
||||
|
||||
if (!result.success) {
|
||||
console.error(result.error)
|
||||
return {}
|
||||
}
|
||||
|
||||
return result.data.data
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((rawModel) => ({
|
||||
...rawModel,
|
||||
modelInfo: {
|
||||
maxTokens: rawModel.top_provider?.max_completion_tokens ?? undefined,
|
||||
contextWindow: rawModel.context_length,
|
||||
inputPrice: parsePrice(rawModel.pricing?.prompt),
|
||||
outputPrice: parsePrice(rawModel.pricing?.completion),
|
||||
description: rawModel.description,
|
||||
supportsPromptCache: false,
|
||||
supportsImages: false,
|
||||
supportsThinking: false,
|
||||
tiers: [],
|
||||
},
|
||||
}))
|
||||
.reduce((acc, model) => {
|
||||
acc[model.id] = model
|
||||
return acc
|
||||
}, {} as OpenRouterModelRecord)
|
||||
}
|
||||
|
||||
export const useOpenRouterModels = () =>
|
||||
useQuery<OpenRouterModelRecord>({ queryKey: ["getOpenRouterModels"], queryFn: getOpenRouterModels })
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
export * from "./format-currency"
|
||||
export * from "./format-duration"
|
||||
export * from "./format-score"
|
||||
export * from "./format-tokens"
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
"use server"
|
||||
|
||||
import { sql } from "drizzle-orm"
|
||||
import { db, tasks } from "@/db"
|
||||
|
||||
export type Language = "go" | "java" | "javascript" | "python" | "rust"
|
||||
|
||||
export const getLanguageScores = async () => {
|
||||
const records = await db
|
||||
.select({
|
||||
runId: tasks.runId,
|
||||
language: sql<Language>`language`,
|
||||
score: sql<number>`cast(sum(case when ${tasks.passed} = 1 then 1 else 0 end) as float) / count(*)`,
|
||||
})
|
||||
.from(tasks)
|
||||
.groupBy(tasks.runId, tasks.language)
|
||||
|
||||
const results: Record<number, Record<Language, number>> = {}
|
||||
|
||||
for (const { runId, language, score } of records) {
|
||||
if (!results[runId]) {
|
||||
results[runId] = { go: 0, java: 0, javascript: 0, python: 0, rust: 0 }
|
||||
}
|
||||
|
||||
results[runId][language] = score
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from "./get-language-scores"
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
export async function getGitHubStars() {
|
||||
try {
|
||||
const res = await fetch("https://api.github.com/repos/RooVetGit/Roo-Code")
|
||||
const data = await res.json()
|
||||
|
||||
if (typeof data.stargazers_count !== "number") {
|
||||
console.error("GitHub API: Invalid stargazers count. Possible that you got rate-limited?")
|
||||
return null
|
||||
}
|
||||
|
||||
return formatNumber(data.stargazers_count)
|
||||
} catch (error) {
|
||||
console.error("Error fetching GitHub stars:", error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function getVSCodeReviews() {
|
||||
const res = await fetch("https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json;api-version=7.1-preview.1",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
filters: [
|
||||
{
|
||||
criteria: [
|
||||
{
|
||||
filterType: 7,
|
||||
value: "RooVeterinaryInc.roo-cline",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
flags: 914,
|
||||
}),
|
||||
})
|
||||
|
||||
try {
|
||||
const data = await res.json()
|
||||
const reviews = data?.results?.[0]?.extensions?.[0]?.reviews
|
||||
|
||||
if (!reviews) {
|
||||
console.error("VSCode API: Missing reviews in response")
|
||||
return []
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
return reviews.map((review: any) => ({
|
||||
name: review.reviewer?.displayName || "Anonymous",
|
||||
rating: review.rating,
|
||||
content: review.text,
|
||||
date: new Date(review.date).toLocaleDateString(),
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error("Error fetching VSCode reviews:", error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export async function getVSCodeDownloads() {
|
||||
const res = await fetch("https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json;api-version=7.1-preview.1",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
filters: [
|
||||
{
|
||||
criteria: [
|
||||
{
|
||||
filterType: 7,
|
||||
value: "RooVeterinaryInc.roo-cline",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
flags: 914,
|
||||
}),
|
||||
})
|
||||
try {
|
||||
const data = await res.json()
|
||||
const statistics = data?.results?.[0]?.extensions?.[0]?.statistics
|
||||
|
||||
if (!statistics) {
|
||||
console.error("VSCode API: Missing statistics in response")
|
||||
return null
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const installStat = statistics.find((stat: any) => stat.statisticName === "install")
|
||||
if (!installStat) {
|
||||
console.error("VSCode API: Install count not found")
|
||||
return null
|
||||
}
|
||||
|
||||
return formatNumber(installStat.value)
|
||||
} catch (error) {
|
||||
console.error("Error fetching VSCode downloads:", error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function formatNumber(num: number): string {
|
||||
// divide by 1000 to convert to "thousands" format,
|
||||
// multiply by 10, floor the result, then divide by 10 to keep one decimal place.
|
||||
const truncated = Math.floor((num / 1000) * 10) / 10
|
||||
// ensure one decimal is always shown and append "k"
|
||||
return truncated.toFixed(1) + "k"
|
||||
|
||||
// examples:
|
||||
// console.log(formatNumber(337231)) -> "337.2k"
|
||||
// console.log(formatNumber(23233)) -> "23.2k"
|
||||
// console.log(formatNumber(2322)) -> "2.3k"
|
||||
// console.log(formatNumber(212)) -> "0.2k"
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
import type { Config } from "tailwindcss"
|
||||
|
||||
const config: Config = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
"./pages/**/*.{ts,tsx}",
|
||||
"./components/**/*.{ts,tsx}",
|
||||
"./app/**/*.{ts,tsx}",
|
||||
"./src/**/*.{ts,tsx}",
|
||||
"*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
"chart-1": {
|
||||
DEFAULT: "hsl(var(--chart-1))",
|
||||
foreground: "hsl(var(--chart-1-foreground))",
|
||||
},
|
||||
"chart-2": {
|
||||
DEFAULT: "hsl(var(--chart-2))",
|
||||
foreground: "hsl(var(--chart-2-foreground))",
|
||||
},
|
||||
"chart-3": {
|
||||
DEFAULT: "hsl(var(--chart-3))",
|
||||
foreground: "hsl(var(--chart-3-foreground))",
|
||||
},
|
||||
"chart-4": {
|
||||
DEFAULT: "hsl(var(--chart-4))",
|
||||
foreground: "hsl(var(--chart-4-foreground))",
|
||||
},
|
||||
"chart-5": {
|
||||
DEFAULT: "hsl(var(--chart-5))",
|
||||
foreground: "hsl(var(--chart-5-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
},
|
||||
"fade-in": {
|
||||
from: { opacity: "0" },
|
||||
to: { opacity: "1" },
|
||||
},
|
||||
"fade-out": {
|
||||
from: { opacity: "1" },
|
||||
to: { opacity: "0" },
|
||||
},
|
||||
"pulse-glow": {
|
||||
"0%, 100%": { opacity: "0.6" },
|
||||
"50%": { opacity: "0.8" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
"fade-in": "fade-in 0.5s ease-out",
|
||||
"fade-out": "fade-out 0.5s ease-out",
|
||||
"pulse-glow": "pulse-glow 3s infinite",
|
||||
},
|
||||
screens: {
|
||||
xs: "420px",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
|
||||
}
|
||||
|
||||
export default config
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"extends": "@roo-code/config-typescript/nextjs.json",
|
||||
"compilerOptions": {
|
||||
"paths": { "@/*": ["./src/*"] }
|
||||
},
|
||||
"include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx", ".next/types/**/*.ts", "drizzle.config.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
@ -354,21 +354,3 @@ export const PROVIDER_SETTINGS_KEYS = keysOf<ProviderSettings>()([
|
|||
"litellmApiKey",
|
||||
"litellmModelId",
|
||||
])
|
||||
|
||||
export const MODEL_ID_KEYS: Partial<keyof ProviderSettings>[] = [
|
||||
"apiModelId",
|
||||
"glamaModelId",
|
||||
"openRouterModelId",
|
||||
"openAiModelId",
|
||||
"ollamaModelId",
|
||||
"lmStudioModelId",
|
||||
"lmStudioDraftModelId",
|
||||
"unboundModelId",
|
||||
"requestyModelId",
|
||||
"litellmModelId",
|
||||
]
|
||||
|
||||
export const getModelId = (settings: ProviderSettings): string | undefined => {
|
||||
const modelIdKey = MODEL_ID_KEYS.find((key) => settings[key])
|
||||
return modelIdKey ? (settings[modelIdKey] as string) : undefined
|
||||
}
|
||||
|
|
|
|||