Merge pull request #849 from tnfssc/chore/model-pricing

This commit is contained in:
Sharath 2025-06-27 23:45:56 +05:30 committed by GitHub
commit 6a658b5a48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 301 additions and 148 deletions

View File

@ -21,7 +21,7 @@ import time
stripe.api_key = config.STRIPE_SECRET_KEY
# Token price multiplier
TOKEN_PRICE_MULTIPLIER = 2
TOKEN_PRICE_MULTIPLIER = 1.5
# Initialize router
router = APIRouter(prefix="/billing", tags=["billing"])
@ -51,7 +51,11 @@ HARDCODED_MODEL_PRICES = {
"openrouter/google/gemini-2.5-flash-preview-05-20": {
"input_cost_per_million_tokens": 0.15,
"output_cost_per_million_tokens": 0.60
}
},
"anthropic/claude-sonnet-4": {
"input_cost_per_million_tokens": 3.00,
"output_cost_per_million_tokens": 15.00,
},
}
def get_model_pricing(model: str) -> tuple[float, float] | None:

View File

@ -1,96 +1,26 @@
'use client';
import { SectionHeader } from '@/components/home/section-header';
import { AlertCircle } from 'lucide-react';
import {
AlertCircle,
Clock,
DollarSign,
Zap,
Server,
Globe,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { useAvailableModels } from '@/hooks/react-query/subscriptions/use-billing';
import type { Model } from '@/lib/api';
import { Loader2 } from 'lucide-react';
import Link from 'next/link';
interface ModelCardProps {
model: Model;
index: number;
}
function ModelCard({ model, index }: ModelCardProps) {
return (
<div
className="group relative bg-gradient-to-br from-background to-background/80 border border-border/50 rounded-2xl p-6 hover:border-border transition-all duration-300 hover:shadow-xl hover:shadow-black/5 hover:-translate-y-1"
style={{ animationDelay: `${index * 100}ms` }}
>
<div className="space-y-4">
{/* Model Header */}
<div className="space-y-2">
<h3 className="text-xl font-bold text-foreground group-hover:text-blue-600 transition-colors duration-300">
{model.display_name}
</h3>
</div>
{/* Pricing Grid */}
<div className="grid grid-cols-2 gap-4">
{/* Input Cost */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span className="text-sm font-medium text-muted-foreground">
Input
</span>
</div>
{model.input_cost_per_million_tokens !== null &&
model.input_cost_per_million_tokens !== undefined ? (
<>
<div className="text-2xl font-bold text-foreground">
${model.input_cost_per_million_tokens.toFixed(2)}
</div>
<div className="text-xs text-muted-foreground">
per 1M tokens
</div>
</>
) : (
<div className="text-2xl font-bold text-muted-foreground"></div>
)}
</div>
{/* Output Cost */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
<span className="text-sm font-medium text-muted-foreground">
Output
</span>
</div>
{model.output_cost_per_million_tokens !== null &&
model.output_cost_per_million_tokens !== undefined ? (
<>
<div className="text-2xl font-bold text-foreground">
${model.output_cost_per_million_tokens.toFixed(2)}
</div>
<div className="text-xs text-muted-foreground">
per 1M tokens
</div>
</>
) : (
<div className="text-2xl font-bold text-muted-foreground"></div>
)}
</div>
</div>
{/* Model Stats */}
{model.max_tokens && (
<div className="pt-4 border-t border-border/50">
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Max Tokens</span>
<span className="font-medium text-foreground">
{model.max_tokens.toLocaleString()}
</span>
</div>
</div>
)}
</div>
</div>
);
}
export default function PricingPage() {
const {
@ -111,77 +41,295 @@ export default function PricingPage() {
);
}) || [];
return (
<div className="relative min-h-screen w-full bg-gradient-to-br from-background via-background to-background/90">
{/* Animated Background */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-blue-50/20 via-transparent to-purple-50/20 dark:from-blue-950/20 dark:to-purple-950/20"></div>
{/* Header */}
<SectionHeader>
<div className="text-center space-y-6">
<div className="space-y-4">
<h1 className="text-5xl md:text-6xl font-bold bg-gradient-to-br from-foreground via-foreground/90 to-foreground/70 bg-clip-text text-transparent">
Compute Pricing
</h1>
</div>
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="flex flex-col items-center gap-4">
<Loader2 className="w-8 h-8 animate-spin text-blue-500" />
<p className="text-sm text-muted-foreground">
Loading pricing data...
</p>
</div>
</SectionHeader>
</div>
);
}
{/* Content */}
<div className="relative z-10 max-w-7xl mx-auto px-6 pb-20 pt-4">
{loading ? (
<div className="flex items-center justify-center py-32">
<div className="flex flex-col items-center gap-6">
<div className="relative">
<div className="w-16 h-16 border-4 border-blue-200 dark:border-blue-800 rounded-full animate-spin border-t-blue-500"></div>
<div className="absolute inset-0 w-16 h-16 border-4 border-transparent rounded-full animate-ping border-t-blue-500/20"></div>
if (error) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="max-w-md text-center space-y-4">
<AlertCircle className="w-12 h-12 text-red-500 mx-auto" />
<div className="space-y-2">
<h3 className="text-lg font-semibold text-foreground">
Pricing Unavailable
</h3>
<p className="text-sm text-muted-foreground">
{error instanceof Error
? error.message
: 'Failed to fetch model pricing'}
</p>
</div>
<Button onClick={() => refetch()} size="sm">
Try Again
</Button>
</div>
</div>
);
}
return (
<div className="space-y-8 p-8 max-w-4xl mx-auto">
{/* Header Section */}
<div className="space-y-4">
<h1 className="text-3xl font-bold text-foreground">
Credits & Pricing
</h1>
<p className="text-lg text-muted-foreground max-w-3xl">
Understand how credits work, explore pricing for AI models, and find
the right plan for your needs.
</p>
</div>
{/* What are Credits Section */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Zap className="w-5 h-5 text-blue-500" />
What are credits?
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">
Credits are our standard unit of measurement for platform usage -
the more complex or lengthy the task, the more credits it requires.
Credits provide a unified way to measure consumption across
different types of AI operations and computational resources.
</p>
</CardContent>
</Card>
{/* How Credits Work Section */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Server className="w-5 h-5 text-green-500" />
How do credits work?
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">
Credits are consumed based on AI model usage. We apply a 50% markup
over the direct model provider costs. The specific credits
consumption is determined by the model used and the number of tokens
processed (both input and output tokens).
</p>
</CardContent>
</Card>
{/* Usage Examples Section */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Globe className="w-5 h-5 text-orange-500" />
Usage Examples
</CardTitle>
<CardDescription>
Here are some examples demonstrating credits consumption across
different task types and complexity levels.
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{/* Example 4 */}
<div className="p-4 border border-border rounded-lg space-y-3">
<div className="space-y-2">
<h4 className="font-semibold text-foreground">
Social Automation System
</h4>
<Badge variant="destructive">Complex</Badge>
</div>
<div className="text-center">
<p className="text-lg font-medium text-foreground">
Loading compute pricing
</p>
<p className="text-sm text-muted-foreground">
Fetching the latest pricing data...
</p>
<div className="space-y-2 text-sm mt-6">
<div className="flex justify-between">
<span className="text-muted-foreground">Model:</span>
<span>claude-sonnet-4</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Duration:</span>
<span>35 minutes</span>
</div>
<div className="flex justify-between font-semibold">
<span className="text-muted-foreground">Cost:</span>
<span className="text-blue-600">$17.45</span>
</div>
</div>
</div>
</div>
) : error ? (
<div className="flex items-center justify-center py-32">
<div className="max-w-md text-center space-y-6">
<div className="w-20 h-20 mx-auto rounded-full bg-gradient-to-br from-red-50 to-red-100 dark:from-red-950/50 dark:to-red-900/50 border border-red-200 dark:border-red-800 flex items-center justify-center">
<AlertCircle className="w-10 h-10 text-red-500" />
{/* Example 6 */}
<div className="p-4 border border-border rounded-lg space-y-3">
<div className="space-y-2">
<h4 className="font-semibold text-foreground">
Content Marketing Strategy
</h4>
<Badge variant="secondary">Standard Complexity</Badge>
</div>
<div className="space-y-3">
<h3 className="text-2xl font-bold text-foreground">
Pricing Unavailable
</h3>
<p className="text-muted-foreground leading-relaxed">
{error instanceof Error
? error.message
: 'Failed to fetch model pricing'}
</p>
<div className="space-y-2 text-sm mt-6">
<div className="flex justify-between">
<span className="text-muted-foreground">Model:</span>
<span>claude-sonnet-4</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Duration:</span>
<span>11 minutes</span>
</div>
<div className="flex justify-between font-semibold">
<span className="text-muted-foreground">Cost:</span>
<span className="text-blue-600">$1.93</span>
</div>
</div>
<Button
onClick={() => refetch()}
className="bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white border-0 shadow-lg hover:shadow-xl transition-all duration-300"
>
<Loader2 className="w-4 h-4 mr-2" />
Try Again
</Button>
</div>
{/* Example 5 */}
<div className="p-4 border border-border rounded-lg space-y-3">
<div className="space-y-2">
<h4 className="font-semibold text-foreground">
Go-to-Market Strategy
</h4>
<Badge variant="secondary">Standard Complexity</Badge>
</div>
<div className="space-y-2 text-sm mt-6">
<div className="flex justify-between">
<span className="text-muted-foreground">Model:</span>
<span>deepseek-chat</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Duration:</span>
<span>3 minutes</span>
</div>
<div className="flex justify-between font-semibold">
<span className="text-muted-foreground">Cost:</span>
<span className="text-blue-600">$0.13</span>
</div>
</div>
</div>
{/* Example 7 */}
{/* <div className="p-4 border border-border rounded-lg space-y-3">
<div className="space-y-2">
<h4 className="font-semibold text-foreground">
6-Month Content Marketing Strategy
</h4>
<Badge variant="secondary">Standard Complexity</Badge>
</div>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Task type:</span>
<span>Marketing SEO Content</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Model:</span>
<span>deepseek-chat</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Duration:</span>
<span>4 minutes</span>
</div>
<div className="flex justify-between font-semibold">
<span className="text-muted-foreground">Cost:</span>
<span className="text-blue-600">$0.20</span>
</div>
</div>
</div> */}
</div>
) : (
<div className="space-y-12">
{/* Model Cards Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
</CardContent>
</Card>
{/* Model Pricing Table */}
<Card>
<CardHeader>
<CardTitle>Compute Pricing by Model</CardTitle>
<CardDescription>
Detailed pricing information for available AI models. We apply a 50%
markup on direct LLM provider costs to maintain our service and
generate profit.
</CardDescription>
</CardHeader>
<CardContent>
<div className="bg-card border border-border rounded-lg">
<div className="px-6 py-4 border-b border-border">
<div className="grid grid-cols-3 gap-4 text-sm font-medium text-muted-foreground">
<div className="col-span-1">Model</div>
<div className="col-span-1 text-center">Input Cost</div>
<div className="col-span-1 text-center">Output Cost</div>
</div>
</div>
<div className="divide-y divide-border">
{models.map((model, index) => (
<ModelCard key={model.id} model={model} index={index} />
<div
key={model.id}
className="px-6 py-4 hover:bg-muted/50 transition-colors duration-150"
>
<div className="grid grid-cols-3 gap-4 items-center">
{/* Model Name */}
<div className="col-span-1">
<div className="flex items-center gap-3">
<div className="w-2 h-2 bg-blue-500 rounded-full flex-shrink-0"></div>
<div className="min-w-0">
<div className="font-medium text-foreground truncate">
{model.display_name}
</div>
</div>
</div>
</div>
{/* Input Cost */}
<div className="col-span-1 text-center">
<div className="space-y-1">
{model.input_cost_per_million_tokens !== null &&
model.input_cost_per_million_tokens !== undefined ? (
<>
<div className="font-semibold text-foreground">
${model.input_cost_per_million_tokens.toFixed(2)}
</div>
<div className="text-xs text-muted-foreground">
per 1M tokens
</div>
</>
) : (
<div className="font-semibold text-muted-foreground">
</div>
)}
</div>
</div>
{/* Output Cost */}
<div className="col-span-1 text-center">
<div className="space-y-1">
{model.output_cost_per_million_tokens !== null &&
model.output_cost_per_million_tokens !== undefined ? (
<>
<div className="font-semibold text-foreground">
${model.output_cost_per_million_tokens.toFixed(2)}
</div>
<div className="text-xs text-muted-foreground">
per 1M tokens
</div>
</>
) : (
<div className="font-semibold text-muted-foreground">
</div>
)}
</div>
</div>
</div>
</div>
))}
</div>
</div>
)}
</div>
</CardContent>
</Card>
</div>
);
}

View File

@ -131,11 +131,12 @@ export default function AccountBillingStatus({ accountId, returnUrl }: Props) {
{/* Manage Subscription Button */}
<div className='flex justify-center items-center gap-4'>
<Button
onClick={() => window.open('/model-pricing', '_blank')}
variant="outline"
className="border-border hover:bg-muted/50 shadow-sm hover:shadow-md transition-all"
>
View Compute Pricing <OpenInNewWindowIcon className='w-4 h-4' />
<Link href="/model-pricing">
View Compute Pricing <OpenInNewWindowIcon className='w-4 h-4' />
</Link>
</Button>
<Button
onClick={handleManageSubscription}