diff --git a/apps/web-roo-code/src/app/pricing/page.tsx b/apps/web-roo-code/src/app/pricing/page.tsx index 9985881e1d..664f0ea904 100644 --- a/apps/web-roo-code/src/app/pricing/page.tsx +++ b/apps/web-roo-code/src/app/pricing/page.tsx @@ -1,4 +1,4 @@ -import { Users, Building2, ArrowRight, Star, LucideIcon, Check, Cloud } from "lucide-react" +import { Users, Building2, ArrowRight, Star, LucideIcon, Check, Cloud, PlugZap } from "lucide-react" import type { Metadata } from "next" import Link from "next/link" @@ -64,7 +64,6 @@ interface PricingTier { period?: string creditPrice?: string trial?: string - cancellation?: string description: string featuresIntro?: string features: string[] @@ -80,13 +79,12 @@ const pricingTiers: PricingTier[] = [ name: "Cloud Free", icon: Cloud, price: "$0", - cancellation: "Cancel anytime", description: "For folks just getting started", features: [ "Token usage analytics", + "Access to the Roo Code Cloud Provider, including early access to free stealth models", "Follow your tasks from anywhere", "Share tasks with friends and co-workers", - "Early access to free AI Models", "Community support", ], cta: { @@ -95,18 +93,18 @@ const pricingTiers: PricingTier[] = [ }, }, { - name: "Pro", + name: "Cloud Pro", icon: Star, price: "$20", period: "/mo", - trial: "Free 14-day trial · ", + trial: "Free for 14 days, then", creditPrice: `$${PRICE_CREDITS}`, - cancellation: "Cancel anytime", description: "For pro Roo coders", featuresIntro: "Everything in Free +", features: [ - "Cloud Agents: PR Reviewer and more", - "Roomote Control: Start, stop and control tasks from anywhere", + "Cloud Agents: Coder, Explainer, Planner, Reviewer, Fixer and more", + "Start tasks from Slack", + "Roomote Control: Start, stop and control extension tasks from anywhere", "Paid support", ], cta: { @@ -115,13 +113,12 @@ const pricingTiers: PricingTier[] = [ }, }, { - name: "Team", + name: "Cloud Team", icon: Users, price: "$99", period: "/mo", creditPrice: `$${PRICE_CREDITS}`, - trial: "Free 14-day trial · ", - cancellation: "Cancel anytime", + trial: "Free for 14 days, then", description: "For AI-forward teams", featuresIntro: "Everything in Pro +", features: ["Unlimited users (no per-seat cost)", "Shared configuration & policies", "Centralized billing"], @@ -151,12 +148,30 @@ export default function PricingPage() { - {/* Free Extension Notice */} -
-
+
+

- The Roo Code extension is free! - Roo Code Cloud is an optional service which takes it to the next level. + The Roo Code extension is totally free! + But Cloud takes you so much further. +

+
+
+ +
+

+ Roo Code Provider + +

+
+

+ On any plan, you can bring your own provider key or use the built-in Roo Code Cloud provider. +

+

+ We offer a select mix of tested state of the art closed and open weight LLMs for you to choose, + with no markup. + + See detailed pricing +

@@ -183,7 +198,7 @@ export default function PricingPage() {

{tier.featuresIntro} 

-
-

- {tier.price} - {tier.period} -

+

{tier.trial}

- {tier.creditPrice && ( -

- + {tier.creditPrice}/hour for Cloud tasks -

- )} +

+ {tier.price} + {tier.period} + prepaid credits +

-

- {tier.trial} - {tier.cancellation} +

+ {tier.creditPrice && ( + <> + Cloud Agents: {tier.creditPrice}/hour if used +
+ + )} + Inference:{" "} + + Roo Provider pricing + {" "} + or{" "} + + BYOK +

{tier.cta.isContactForm ? ( @@ -249,7 +272,7 @@ export default function PricingPage() {

Frequently Asked Questions

-
+

Wait, is Roo Code free or not?

Yes! The Roo Code VS Code extension is open source and free forever. The extension acts @@ -257,7 +280,7 @@ export default function PricingPage() { Code Cloud.

-
+

Is there a free trial?

Yes, all paid plans come with a 14-day free trial to try out functionality. @@ -266,12 +289,25 @@ export default function PricingPage() { To use Cloud Agents, you can buy credits.

-
-

How do Cloud Agent credits work?

+
+

How do credits work?

- Cloud Agents are a version of Roo running in the cloud without depending on your IDE. - You can run as many as you want, and bring your own inference provider key. + Roo Code Cloud credits can be used in two ways:

+
    +
  • To pay for Cloud Agents running time (${PRICE_CREDITS}/hour)
  • +
  • + To pay for AI model inference costs ( + + varies by model + + ) +
  • +

To cover our infrastructure costs, we charge ${PRICE_CREDITS}/hour while the agent is running (independent of inference costs). @@ -280,25 +316,25 @@ export default function PricingPage() { There are no markups, no tiers, no dumbing-down of models to increase our profit.

-
+

Do I need a credit card for the free trial?

Yes, but you won't be charged until your trial ends, except for credit purchases.

You can cancel anytime with one click.

-
+

What payment methods do you accept?

We accept all major credit cards, debit cards, and can arrange invoice billing for Enterprise customers.

-
-

Can I change plans anytime?

+
+

Can I cancel or change plans?

- Yes, you can upgrade or downgrade your plan at any time. Changes will be reflected in - your next billing cycle. + Yes, you can upgrade, downgrade or cancel your plan at any time. Changes will be + reflected in your next billing cycle.

diff --git a/apps/web-roo-code/src/app/provider/pricing/components/model-card.tsx b/apps/web-roo-code/src/app/provider/pricing/components/model-card.tsx new file mode 100644 index 0000000000..26f3545791 --- /dev/null +++ b/apps/web-roo-code/src/app/provider/pricing/components/model-card.tsx @@ -0,0 +1,190 @@ +import { ModelWithTotalPrice } from "@/lib/types/models" +import { formatCurrency, formatTokens } from "@/lib/formatters" +import { + ArrowLeftToLine, + ArrowRightToLine, + Building2, + Check, + Expand, + Gift, + HardDriveDownload, + HardDriveUpload, + RulerDimensionLine, + ChevronDown, + ChevronUp, +} from "lucide-react" +import { useState } from "react" + +interface ModelCardProps { + model: ModelWithTotalPrice +} + +export function ModelCard({ model }: ModelCardProps) { + // Prices are per token, multiply by 1M to get price per million tokens + const inputPrice = parseFloat(model.pricing.input) * 1_000_000 + const outputPrice = parseFloat(model.pricing.output) * 1_000_000 + const cacheReadPrice = parseFloat(model.pricing.input_cache_read || "0") * 1_000_000 + const cacheWritePrice = parseFloat(model.pricing.input_cache_write || "0") * 1_000_000 + + const free = model.tags.includes("free") + // Filter tags to only show vision and reasoning + const displayTags = model.tags.filter((tag) => tag === "vision" || tag === "reasoning") + + // Mobile collapsed/expanded state + const [expanded, setExpanded] = useState(false) + + return ( +
+ {/* Header: always visible */} +
+

+ {model.name} + {free && ( + + + Free! + + )} +

+

+ {model.description} +

+
+ + {/* Content - pinned to bottom */} +
+ + + {/* Provider: always visible if present */} + {model.owned_by && ( + + + + + )} + + {/* Context Window: always visible */} + + + + + + {/* Max Output Tokens: always visible on >=sm, expandable on mobile */} + + + + + + {/* Input Price: always visible */} + + + + + + {/* Output Price: always visible */} + + + + + + {/* Cache pricing: only visible on mobile when expanded, always visible on >=sm */} + {cacheReadPrice > 0 && ( + + + + + )} + + {cacheWritePrice > 0 && ( + + + + + )} + + {/* Tags row: only show if there are vision or reasoning tags */} + {displayTags.length > 0 && ( + + + + + )} + + {/* Mobile-only toggle row */} + + + + +
+ + Provider + {model.owned_by}
+ + Context Window + {formatTokens(model.context_window)}
+ + Max Output Tokens + {formatTokens(model.max_tokens)}
+ + Input Price + + {inputPrice === 0 ? "Free" : `${formatCurrency(inputPrice)}/1M tokens`} +
+ + Output Price + + {outputPrice === 0 ? "Free" : `${formatCurrency(outputPrice)}/1M tokens`} +
+ + Cache Read + {formatCurrency(cacheReadPrice)}/1M tokens
+ + Cache Write + {formatCurrency(cacheWritePrice)}/1M tokens
Features + {displayTags.map((tag) => ( + + + {tag} + + ))} +
+ +
+
+
+ ) +} diff --git a/apps/web-roo-code/src/app/provider/pricing/page.tsx b/apps/web-roo-code/src/app/provider/pricing/page.tsx new file mode 100644 index 0000000000..4558355d2c --- /dev/null +++ b/apps/web-roo-code/src/app/provider/pricing/page.tsx @@ -0,0 +1,253 @@ +"use client" + +import { useEffect, useMemo, useState } from "react" +import { ModelCard } from "./components/model-card" +import { Model, ModelWithTotalPrice, ModelsResponse, SortOption } from "@/lib/types/models" +import Link from "next/link" +import { ChevronDown, CircleX, Loader, LoaderCircle, Search } from "lucide-react" + +const API_URL = "https://api.roocode.com/proxy/v1/models?include_paid=true" + +const faqs = [ + { + question: "What are AI model providers?", + answer: "AI model providers offer various language models with different capabilities and pricing.", + }, + { + question: "How is pricing calculated?", + answer: "Pricing is based on token usage for input and output, measured per million tokens, like pretty much any other provider out there.", + }, + { + question: "What is the Roo Code Cloud Provider?", + answer: ( + <> +

This is our very own model provider, optimized to work seamlessly with Roo Code Cloud.

+

+ It offers a selection of state-of-the-art LLMs (both closed and open weight) we know work well with + Roo for you to choose, with no markup. +

+

+ We also often feature 100% free models which labs share with us for the community to use and provide + feedback. +

+ + ), + }, + { + question: "But how much does the Roo Code Cloud service cost?", + answer: ( + <> + Our{" "} + + service pricing is here. + + + ), + }, +] + +function calculateTotalPrice(model: Model): number { + return parseFloat(model.pricing.input) + parseFloat(model.pricing.output) +} + +function enrichModelWithTotalPrice(model: Model): ModelWithTotalPrice { + return { + ...model, + totalPrice: calculateTotalPrice(model), + } +} + +export default function ProviderPricingPage() { + const [models, setModels] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + const [searchQuery, setSearchQuery] = useState("") + const [sortOption, setSortOption] = useState("alphabetical") + + useEffect(() => { + async function fetchModels() { + try { + setLoading(true) + setError(null) + const response = await fetch(API_URL) + if (!response.ok) { + throw new Error(`Failed to fetch models: ${response.statusText}`) + } + const data: ModelsResponse = await response.json() + const enrichedModels = data.data.map(enrichModelWithTotalPrice) + setModels(enrichedModels) + } catch (err) { + setError(err instanceof Error ? err.message : "An error occurred while fetching models") + } finally { + setLoading(false) + } + } + + fetchModels() + }, []) + + const filteredAndSortedModels = useMemo(() => { + // Filter out deprecated models + let filtered = models.filter((model) => !model.deprecated) + + // Filter by search query + if (searchQuery.trim()) { + const query = searchQuery.toLowerCase() + filtered = filtered.filter((model) => { + return ( + model.name.toLowerCase().includes(query) || + model.owned_by?.toLowerCase().includes(query) || + model.description.toLowerCase().includes(query) + ) + }) + } + + // Sort filtered results + const sorted = [...filtered] + switch (sortOption) { + case "alphabetical": + sorted.sort((a, b) => a.name.localeCompare(b.name)) + break + case "price-asc": + sorted.sort((a, b) => a.totalPrice - b.totalPrice) + break + case "price-desc": + sorted.sort((a, b) => b.totalPrice - a.totalPrice) + break + case "context-window-asc": + sorted.sort((a, b) => a.context_window - b.context_window) + break + case "context-window-desc": + sorted.sort((a, b) => b.context_window - a.context_window) + break + } + + return sorted + }, [models, searchQuery, sortOption]) + + // Count non-deprecated models for the display + const nonDeprecatedCount = useMemo(() => models.filter((model) => !model.deprecated).length, [models]) + + return ( + <> +
+
+
+

+ Roo Code Cloud Provider Pricing +

+

+ See pricing and features for all models we offer in our selection. +
+ You can always bring your own key ( + + FAQ + + ). +

+
+
+
+ +
+
+
+
+
+
+
+ + setSearchQuery(e.target.value)} + className="w-full rounded-full border border-input bg-background px-10 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" + /> + +
+ {filteredAndSortedModels.length} of {nonDeprecatedCount} models +
+
+
+
+
+ + +
+
+
+
+
+ +
+
+ {loading && ( +
+ +

Loading model list...

+
+ )} + + {error && ( +
+ +

Oops, couldn't load the model list.

+

Try again in a bit please.

+
+ )} + + {!loading && !error && filteredAndSortedModels.length === 0 && ( +
+ +

No models match your search.

+

+ Keep in mind we don't have every model under the sun – only the ones we think + are worth using. +
+ You can always use a third-party provider to access a wider selection. +

+
+ )} + + {!loading && !error && filteredAndSortedModels.length > 0 && ( +
+ {filteredAndSortedModels.map((model) => ( + + ))} +
+ )} +
+
+
+ + {/* FAQ Section */} +
+ +
+
+

Frequently Asked Questions

+
+
+ {faqs.map((faq, index) => ( +
+

{faq.question}

+

{faq.answer}

+
+ ))} +
+
+
+ + ) +} diff --git a/apps/web-roo-code/src/lib/formatters.ts b/apps/web-roo-code/src/lib/formatters.ts new file mode 100644 index 0000000000..88ca558764 --- /dev/null +++ b/apps/web-roo-code/src/lib/formatters.ts @@ -0,0 +1,22 @@ +const formatter = new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", +}) + +export const formatCurrency = (amount: number) => formatter.format(amount) + +export const formatTokens = (tokens: number) => { + if (tokens < 1000) { + return tokens.toString() + } + + if (tokens < 1000000) { + return `${(tokens / 1000).toFixed(1)}K` + } + + if (tokens < 1000000000) { + return `${(tokens / 1000000).toFixed(1)}M` + } + + return `${(tokens / 1000000000).toFixed(1)}B` +} diff --git a/apps/web-roo-code/src/lib/types/models.ts b/apps/web-roo-code/src/lib/types/models.ts new file mode 100644 index 0000000000..4bcd3116ab --- /dev/null +++ b/apps/web-roo-code/src/lib/types/models.ts @@ -0,0 +1,31 @@ +export interface ModelPricing { + input: string + output: string + input_cache_read: string + input_cache_write: string +} + +export interface Model { + id: string + object: string + created: number + owned_by: string + name: string + description: string + context_window: number + max_tokens: number + type: string + tags: string[] + pricing: ModelPricing + deprecated?: boolean +} + +export interface ModelsResponse { + data: Model[] +} + +export interface ModelWithTotalPrice extends Model { + totalPrice: number +} + +export type SortOption = "alphabetical" | "price-asc" | "price-desc" | "context-window-asc" | "context-window-desc"