refactor(pricing): enhance model filtering and update terminology for clarity

This commit is contained in:
sharath 2025-06-28 15:09:48 +00:00
parent 8a0a8f37b1
commit ca321f7cb2
No known key found for this signature in database
1 changed files with 151 additions and 142 deletions

View File

@ -1,13 +1,6 @@
'use client'; 'use client';
import { import { AlertCircle, Zap, Server, Globe } from 'lucide-react';
AlertCircle,
Clock,
DollarSign,
Zap,
Server,
Globe,
} from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { import {
Card, Card,
@ -16,7 +9,6 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from '@/components/ui/card'; } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { import {
Select, Select,
SelectContent, SelectContent,
@ -27,7 +19,8 @@ import {
import { useAvailableModels } from '@/hooks/react-query/subscriptions/use-billing'; import { useAvailableModels } from '@/hooks/react-query/subscriptions/use-billing';
import type { Model } from '@/lib/api'; import type { Model } from '@/lib/api';
import { Loader2 } from 'lucide-react'; import { Loader2 } from 'lucide-react';
import { useState } from 'react'; import { useMemo, useState } from 'react';
import { useModelSelection } from '@/components/thread/chat-input/_use-model-selection';
// Example task data with token usage // Example task data with token usage
const exampleTasks = [ const exampleTasks = [
@ -141,6 +134,8 @@ const exampleTasks = [
}, },
]; ];
const DISABLE_EXAMPLES = true;
export default function PricingPage() { export default function PricingPage() {
const { const {
data: modelsResponse, data: modelsResponse,
@ -149,21 +144,33 @@ export default function PricingPage() {
refetch, refetch,
} = useAvailableModels(); } = useAvailableModels();
const { allModels } = useModelSelection();
const [selectedModelId, setSelectedModelId] = useState<string>( const [selectedModelId, setSelectedModelId] = useState<string>(
'anthropic/claude-sonnet-4-20250514', 'anthropic/claude-sonnet-4-20250514',
); );
const [showAllTasks, setShowAllTasks] = useState<boolean>(false); const [showAllTasks, setShowAllTasks] = useState<boolean>(false);
// Filter to only show models that have pricing information available // Filter to only show models that have pricing information available and sort by display name order
const models = const models = useMemo(() => {
modelsResponse?.models?.filter((model: Model) => { const filteredModels =
return ( modelsResponse?.models?.filter((model: Model) => {
model.input_cost_per_million_tokens !== null && return (
model.input_cost_per_million_tokens !== undefined && model.input_cost_per_million_tokens !== null &&
model.output_cost_per_million_tokens !== null && model.input_cost_per_million_tokens !== undefined &&
model.output_cost_per_million_tokens !== undefined model.output_cost_per_million_tokens !== null &&
); model.output_cost_per_million_tokens !== undefined
}) || []; );
}) || [];
return filteredModels
.map((v) => ({
...v,
display_name: allModels.find((m) => m.id === v.short_name)?.label,
priority: allModels.find((m) => m.id === v.short_name)?.priority,
}))
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
}, [modelsResponse?.models, allModels]);
// Find the selected model // Find the selected model
const selectedModel = models.find((model) => model.id === selectedModelId); const selectedModel = models.find((model) => model.id === selectedModelId);
@ -229,166 +236,168 @@ export default function PricingPage() {
<div className="space-y-8 p-8 max-w-4xl mx-auto"> <div className="space-y-8 p-8 max-w-4xl mx-auto">
{/* Header Section */} {/* Header Section */}
<div className="space-y-4"> <div className="space-y-4">
<h1 className="text-3xl font-bold text-foreground"> <h1 className="text-3xl font-bold text-foreground">Token Pricing</h1>
Credits & Pricing
</h1>
<p className="text-lg text-muted-foreground max-w-3xl"> <p className="text-lg text-muted-foreground max-w-3xl">
Understand how credits work, explore pricing for AI models, and find Understand how tokens work, explore pricing for AI models, and find
the right plan for your needs. the right plan for your needs.
</p> </p>
</div> </div>
{/* What are Credits Section */} {/* What are Tokens Section */}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="flex items-center gap-2"> <CardTitle className="flex items-center gap-2">
<Zap className="w-5 h-5 text-blue-500" /> <Zap className="w-5 h-5 text-blue-500" />
What are credits? Understanding Tokens & Compute
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Credits are our standard unit of measurement for platform usage - Tokens are the fundamental units that AI models use to process text
the more complex or lengthy the task, the more credits it requires. - the more complex or lengthy your task, the more tokens it
Credits provide a unified way to measure consumption across requires. Compute usage is measured by both input tokens (your
different types of AI operations and computational resources. prompts and context) and output tokens (the AI's responses), with
different models having varying computational requirements and costs
per token.
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
{/* How Credits Work Section */} {/* How Pricing Works Section */}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="flex items-center gap-2"> <CardTitle className="flex items-center gap-2">
<Server className="w-5 h-5 text-green-500" /> <Server className="w-5 h-5 text-green-500" />
How do credits work? How does pricing work?
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Credits are consumed based on AI model usage. We apply a 50% markup Usage costs are calculated based on token consumption from AI model
over the direct model provider costs. The specific credits interactions. We apply a 50% markup over direct model provider costs
consumption is determined by the model used and the number of tokens to maintain our platform and services. Your total cost depends on
processed (both input and output tokens). the specific model used and the number of tokens processed for both
input (prompts, context) and output (generated responses).
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
{/* Usage Examples Section */} {/* Usage Examples Section */}
<Card> {!DISABLE_EXAMPLES && (
<CardHeader> <Card>
<CardTitle className="flex items-center gap-2"> <CardHeader>
<Globe className="w-5 h-5 text-orange-500" /> <CardTitle className="flex items-center gap-2">
Usage Examples <Globe className="w-5 h-5 text-orange-500" />
</CardTitle> Usage Examples
<CardDescription> </CardTitle>
Here are some examples demonstrating credits consumption across <CardDescription>
different task types and complexity levels. Here are some examples demonstrating credits consumption across
</CardDescription> different task types and complexity levels.
</CardHeader> </CardDescription>
<CardContent> </CardHeader>
<div className="space-y-6"> <CardContent>
{/* Model Selection */} <div className="space-y-6">
<div className="space-y-2"> {/* Model Selection */}
<label className="text-sm font-medium text-foreground"> <div className="space-y-2">
Select a model to see pricing: <label className="text-sm font-medium text-foreground">
</label> Select a model to see pricing:
<Select </label>
value={selectedModelId} <Select
onValueChange={setSelectedModelId} value={selectedModelId}
> onValueChange={setSelectedModelId}
<SelectTrigger className="w-full max-w-md"> >
<SelectValue placeholder="Choose a model to calculate costs" /> <SelectTrigger className="w-full max-w-md">
</SelectTrigger> <SelectValue placeholder="Choose a model to calculate costs" />
<SelectContent> </SelectTrigger>
{models.map((model) => ( <SelectContent>
<SelectItem key={model.id} value={model.id}> {models.map((model) => (
{model.display_name} <SelectItem key={model.id} value={model.id}>
</SelectItem> {model.display_name}
))} </SelectItem>
</SelectContent> ))}
</Select> </SelectContent>
</div> </Select>
</div>
{/* Example Tasks Grid */} {/* Example Tasks Grid */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{(showAllTasks ? exampleTasks : exampleTasks.slice(0, 3)).map( {(showAllTasks ? exampleTasks : exampleTasks.slice(0, 3)).map(
(task, index) => { (task, index) => {
const calculatedCost = selectedModel const calculatedCost = selectedModel
? calculateCost( ? calculateCost(
task.inputTokens, task.inputTokens,
task.outputTokens, task.outputTokens,
selectedModel, selectedModel,
) )
: null; : null;
return ( return (
<div <div
key={index} key={index}
className="p-4 border border-border rounded-lg space-y-3" className="p-4 border border-border rounded-lg space-y-3"
> >
<div className="space-y-2"> <div className="space-y-2">
<h4 className="font-semibold text-foreground"> <h4 className="font-semibold text-foreground">
{task.name} {task.name}
</h4> </h4>
</div>
<div className="space-y-2 text-sm mt-6">
<div className="flex justify-between">
<span className="text-muted-foreground">Model:</span>
<span>
{selectedModel?.display_name || task.originalModel}
</span>
</div> </div>
<div className="flex justify-between"> <div className="space-y-2 text-sm mt-6">
<span className="text-muted-foreground"> <div className="flex justify-between">
Input Tokens:
</span>
<span>{task.inputTokens.toLocaleString()}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">
Output Tokens:
</span>
<span>{task.outputTokens.toLocaleString()}</span>
</div>
{/* <div className="flex justify-between">
<span className="text-muted-foreground">Duration:</span>
<span>{task.duration}</span>
</div> */}
<div className="flex justify-between font-semibold">
<span className="text-muted-foreground">Cost:</span>
{calculatedCost !== null ? (
<span className="text-blue-600">
${calculatedCost.toFixed(2)}
</span>
) : (
<span className="text-muted-foreground"> <span className="text-muted-foreground">
Select model above Model:
</span> </span>
)} <span>
{selectedModel?.display_name ||
task.originalModel}
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">
Input Tokens:
</span>
<span>{task.inputTokens.toLocaleString()}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">
Output Tokens:
</span>
<span>{task.outputTokens.toLocaleString()}</span>
</div>
<div className="flex justify-between font-semibold">
<span className="text-muted-foreground">Cost:</span>
{calculatedCost !== null ? (
<span className="text-blue-600">
${calculatedCost.toFixed(2)}
</span>
) : (
<span className="text-muted-foreground">
Select model above
</span>
)}
</div>
</div> </div>
</div> </div>
</div> );
); },
}, )}
</div>
{/* Show More/Less Button */}
{exampleTasks.length > 3 && (
<div className="flex justify-center mt-6">
<Button
variant="outline"
onClick={() => setShowAllTasks(!showAllTasks)}
className="gap-2"
>
{showAllTasks ? 'Show Less' : `Show More`}
</Button>
</div>
)} )}
</div> </div>
</CardContent>
{/* Show More/Less Button */} </Card>
{exampleTasks.length > 3 && ( )}
<div className="flex justify-center mt-6">
<Button
variant="outline"
onClick={() => setShowAllTasks(!showAllTasks)}
className="gap-2"
>
{showAllTasks ? 'Show Less' : `Show More`}
</Button>
</div>
)}
</div>
</CardContent>
</Card>
{/* Model Pricing Table */} {/* Model Pricing Table */}
<Card> <Card>
@ -427,7 +436,7 @@ export default function PricingPage() {
<div className="w-2 h-2 bg-blue-500 rounded-full flex-shrink-0"></div> <div className="w-2 h-2 bg-blue-500 rounded-full flex-shrink-0"></div>
<div className="min-w-0"> <div className="min-w-0">
<div className="font-medium text-foreground truncate"> <div className="font-medium text-foreground truncate">
{model.display_name} {model.display_name ?? model.id}
</div> </div>
</div> </div>
</div> </div>