Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
266 changes: 266 additions & 0 deletions apps/web-roo-code/src/app/reviewer/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
import { ArrowRight, Blocks, BookMarked, ListChecks, LucideIcon } from "lucide-react"
import type { Metadata } from "next"

import { Button } from "@/components/ui"
import { AnimatedBackground } from "@/components/homepage"
import { AgentCarousel } from "@/components/reviewer/agent-carousel"
import { SEO } from "@/lib/seo"
import { EXTERNAL_LINKS } from "@/lib/constants"
import Image from "next/image"

const TITLE = "PR Reviewer · Roo Code Cloud"
const DESCRIPTION =
"Get comprehensive AI-powered PR reviews that save you time, not tokens. Bring your own API key and leverage advanced reasoning, repository-aware analysis, and actionable feedback to keep your PR queue moving."
const PATH = "/reviewer"
const OG_IMAGE = SEO.ogImage

export const metadata: Metadata = {
title: TITLE,
description: DESCRIPTION,
alternates: {
canonical: `${SEO.url}${PATH}`,
},
openGraph: {
title: TITLE,
description: DESCRIPTION,
url: `${SEO.url}${PATH}`,
siteName: SEO.name,
images: [
{
url: OG_IMAGE.url,
width: OG_IMAGE.width,
height: OG_IMAGE.height,
alt: OG_IMAGE.alt,
},
],
locale: SEO.locale,
type: "website",
},
twitter: {
card: SEO.twitterCard,
title: TITLE,
description: DESCRIPTION,
images: [OG_IMAGE.url],
},
keywords: [
...SEO.keywords,
"PR reviewer",
"code review",
"pull request review",
"AI code review",
"GitHub PR review",
"automated code review",
"repository-aware review",
"bring your own key",
"BYOK AI",
"code quality",
"development workflow",
"cloud agents",
"AI development team",
],
}

interface Feature {
icon: LucideIcon
title: string
description: string | React.ReactNode
logos?: string[]
}

const howItWorks: Feature[] = [
{
icon: Blocks,
title: "Our agents, your provider keys",
description: (
<>
<p>
We orchestrate the review, optimize the hell out of the prompts, integrate with GitHub, keep you
properly posted.
</p>
<p>We&apos;re thoughtful about token usage, but not incentivized to skimp to grow our margins.</p>
</>
),
},
{
icon: ListChecks,
title: "Advanced reasoning and workflows",
description:
"We optimize for state-of-the-art reasoning models and leverage powerful workflows (Diff analysis → Context Gathering → Impact Mapping → Contract checks) to produce crisp, actionable comments at the right level.",
},
{
icon: BookMarked,
title: "Fully repository-aware",
description:
"Reviews traverse code ownership, dependency graphs, and historical patterns to surface risk and deviations, not noise.",
},
]

// Workaround for next/image choking on these for some reason
import hero from "/public/heroes/agent-reviewer.png"

export default function AgentReviewerPage() {
return (
<>
<section className="relative flex md:h-[calc(70vh-theme(spacing.12))] items-center overflow-hidden">
<AnimatedBackground />
<div className="container relative flex items-center h-full z-10 mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid h-full relative gap-4 md:gap-20 lg:grid-cols-2">
<div className="flex flex-col px-4 justify-center space-y-6 sm:space-y-8">
<div>
<h1 className="text-3xl font-bold tracking-tight mt-8 md:text-left md:text-4xl lg:text-5xl lg:mt-0">
Get comprehensive reviews that save you time, not&nbsp;tokens.
</h1>
<div className="mt-4 max-w-lg space-y-4 text-base text-muted-foreground md:text-left sm:mt-6">
<p>
Regular AI code review tools cap model usage to protect their margins from fixed
monthly prices. That leads to shallow prompts, limited context, and missed
issues.
</p>
<p>
Roo Code&apos;s PR Reviewer flips the model: you bring your own key and leverage
it to the max – to find real issues, increase code quality and keep your PR
queue moving.
</p>
</div>
</div>
<div className="flex flex-col space-y-3 sm:flex-row sm:space-x-4 sm:space-y-0 md:items-center">
<Button
size="lg"
className="w-full sm:w-auto backdrop-blur-sm border hover:shadow-[0_0_20px_rgba(59,130,246,0.5)] transition-all duration-300">
<a
href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP_PRO}
target="_blank"
rel="noopener noreferrer"
className="flex w-full items-center justify-center">
Start 14-day Free Trial
<ArrowRight className="ml-2" />
</a>
</Button>
<span className="text-sm text-center md:text-left text-muted-foreground md:ml-2">
(cancel anytime)
</span>
</div>
</div>
<div className="flex items-center justify-end mx-auto h-full mt-8 lg:mt-0">
<div className="md:w-[800px] md:h-[474px] relative overflow-clip">
<div className="block">
<Image
src={hero}
alt="Example of a code review generated by Roo Code PR Reviewer"
className="max-w-full h-auto"
width={800}
height={474}
/>
</div>
</div>
</div>
</div>
</div>
</section>

<section className="relative overflow-hidden border-t border-border py-32">
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
<div className="mx-auto mb-12 md:mb-24 max-w-5xl text-center">
<div>
<h2 className="text-4xl font-bold tracking-tight sm:text-5xl">
Why Roo&apos;s PR Reviewer is so much better
</h2>
</div>
</div>

<div className="relative mx-auto md:max-w-[1200px]">
<ul className="grid grid-cols-1 place-items-center gap-6 md:grid-cols-2 lg:grid-cols-3 lg:gap-8">
{howItWorks.map((feature, index) => {
const Icon = feature.icon
return (
<li
key={index}
className="relative h-full border border-border rounded-2xl bg-background p-8 transition-all duration-300">
<Icon className="size-6 text-foreground/80" />
<h3 className="mb-3 mt-3 text-xl font-semibold text-foreground">
{feature.title}
</h3>
<div className="leading-relaxed font-light text-muted-foreground space-y-2">
{feature.description}
</div>
{feature.logos && (
<div className="mt-4 flex flex-wrap items-center gap-4">
{feature.logos.map((logo) => (
<Image
key={logo}
width={20}
height={20}
className="w-5 h-5 overflow-clip opacity-50 dark:invert"
src={`/logos/${logo.toLowerCase()}.svg`}
alt={`${logo} Logo`}
/>
))}
</div>
)}
</li>
)
})}
</ul>
</div>
</div>
</section>

<section className="relative overflow-hidden border-t border-border py-32">
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
<div className="mx-auto mb-12 max-w-4xl text-center">
<div>
<h2 className="text-4xl font-bold tracking-tight sm:text-5xl">
The first member of a whole new team
</h2>

<p className="mt-6 text-lg text-muted-foreground">
Architecture, coding, reviewing, testing, debugging, documenting, designing –{" "}
<em>almost everything</em> we do today is mostly through our agents. Now we&apos;re
bringing them to you.
</p>
<p className="mt-2 text-lg text-muted-foreground">
Roo&apos;s PR Reviewer isn&apos;t yet another single-purpose tool to add to your already
complicated stack.
<br />
It&apos;s the first member of your AI-powered development team. More agents are shipping
soon.
</p>
</div>
</div>

<div className="relative mx-auto md:max-w-[1200px]">
<AgentCarousel />
</div>
</div>
</section>

{/* CTA Section */}
<section className="py-20">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-4xl rounded-3xl border border-border/50 bg-gradient-to-br from-blue-500/5 via-cyan-500/5 to-purple-500/5 p-8 text-center shadow-2xl backdrop-blur-xl dark:border-white/20 dark:bg-gradient-to-br dark:from-gray-800 dark:via-gray-900 dark:to-black sm:p-12">
<h2 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl">Stop wasting time.</h2>
<p className="mx-auto mb-8 max-w-2xl text-lg text-muted-foreground">
Give Roo Code&apos;s PR Reviewer your model key and turn painful reviews into a tangible
quality advantage.
</p>
<div className="flex flex-col justify-center space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0">
<Button
size="lg"
className="bg-black text-white hover:bg-gray-800 hover:shadow-lg hover:shadow-black/20 dark:bg-white dark:text-black dark:hover:bg-gray-200 dark:hover:shadow-white/20 transition-all duration-300"
asChild>
<a
href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP_PRO}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center">
Start 14-day Free Trial
<ArrowRight className="ml-2 h-4 w-4" />
</a>
</Button>
</div>
</div>
</div>
</section>
</>
)
}
125 changes: 125 additions & 0 deletions apps/web-roo-code/src/components/reviewer/agent-carousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"use client"

import { useEffect } from "react"
import { motion } from "framer-motion"
import useEmblaCarousel from "embla-carousel-react"
import AutoPlay from "embla-carousel-autoplay"
import { Bug, FileText, Gauge, Languages, Microscope, PocketKnife, TestTube, type LucideIcon } from "lucide-react"

// AI Agent types for the carousel
interface AIAgent {
icon: LucideIcon
name: string
}

const aiAgents: AIAgent[] = [
{ icon: PocketKnife, name: "Generalist" },
{ icon: Bug, name: "Bug Fixer" },
{ icon: TestTube, name: "Test Engineer" },
{ icon: Microscope, name: "Security Auditor" },
{ icon: Gauge, name: "Performance Optimizer" },
{ icon: FileText, name: "Documentation Writer" },
{ icon: Languages, name: "String Translator" },
]

export function AgentCarousel() {
const [emblaRef, emblaApi] = useEmblaCarousel(
{
loop: true,
align: "start",
watchDrag: true,
dragFree: false,
containScroll: false,
duration: 10000,
},
[
AutoPlay({
playOnInit: true,
delay: 0,
stopOnInteraction: false,
stopOnMouseEnter: false,
stopOnFocusIn: false,
}),
],
)

// Continuous scrolling effect
useEffect(() => {
if (!emblaApi) return

const autoPlay = emblaApi?.plugins()?.autoPlay as
| {
play?: () => void
}
| undefined

if (autoPlay?.play) {
autoPlay.play()
}

// Set up continuous scrolling
const interval = setInterval(() => {
if (emblaApi) {
emblaApi.scrollNext()
}
}, 30) // Smooth continuous scroll

return () => clearInterval(interval)
}, [emblaApi])

const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
duration: 0.6,
ease: [0.21, 0.45, 0.27, 0.9],
},
},
}

// Duplicate the agents array for seamless infinite scroll
const displayAgents = [...aiAgents, ...aiAgents]

return (
<motion.div
className="relative -mx-4 md:mx-auto max-w-[1400px]"
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}>
{/* Gradient Overlays */}
<div className="absolute inset-y-0 left-0 z-10 w-[10%] bg-gradient-to-r from-background to-transparent pointer-events-none md:w-[15%]" />
<div className="absolute inset-y-0 right-0 z-10 w-[10%] bg-gradient-to-l from-background to-transparent pointer-events-none md:w-[15%]" />

{/* Embla Carousel Container */}
<div className="overflow-hidden" ref={emblaRef}>
<div className="flex pb-4">
{displayAgents.map((agent, index) => {
const Icon = agent.icon
return (
<div
key={`${agent.name}-${index}`}
className="relative min-w-0 flex-[0_0_45%] px-2 md:flex-[0_0_30%] md:px-4 lg:flex-[0_0_15%]">
<div className="group relative py-6 cursor-default">
<div
className="relative flex flex-col items-center justify-center rounded-full w-[150px] h-[150px] border border-border bg-background p-6 transition-all duration-500 ease-out shadow-xl
hover:scale-110 hover:-translate-y-2
hover:shadow-[0_20px_50px_rgba(39,110,226,0.25)] dark:hover:shadow-[0_20px_50px_rgba(59,130,246,0.25)]">
<Icon
strokeWidth={1}
className="size-9 mb-2 text-foreground transition-colors duration-300"
/>
<h3 className="text-center leading-tight tracking-tight font-medium text-foreground/90 transition-colors duration-300 dark:text-foreground">
{agent.name}
</h3>
</div>
</div>
</div>
)
})}
</div>
</div>
</motion.div>
)
}
Loading
Loading