mirror of https://github.com/kortix-ai/suna.git
refactor(pricing): enhance model filtering and update terminology for clarity
This commit is contained in:
parent
8a0a8f37b1
commit
ca321f7cb2
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue