mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-19 19:20:49 +08:00
165 lines
5.9 KiB
TypeScript
165 lines
5.9 KiB
TypeScript
import { useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Card } from '@/components/ui/Card';
|
|
import { Button } from '@/components/ui/Button';
|
|
import { Input } from '@/components/ui/Input';
|
|
import type { ModelPrice } from '@/utils/usage';
|
|
import styles from '@/pages/UsagePage.module.scss';
|
|
|
|
export interface PriceSettingsCardProps {
|
|
modelNames: string[];
|
|
modelPrices: Record<string, ModelPrice>;
|
|
onPricesChange: (prices: Record<string, ModelPrice>) => void;
|
|
}
|
|
|
|
export function PriceSettingsCard({
|
|
modelNames,
|
|
modelPrices,
|
|
onPricesChange
|
|
}: PriceSettingsCardProps) {
|
|
const { t } = useTranslation();
|
|
|
|
const [selectedModel, setSelectedModel] = useState('');
|
|
const [promptPrice, setPromptPrice] = useState('');
|
|
const [completionPrice, setCompletionPrice] = useState('');
|
|
const [cachePrice, setCachePrice] = useState('');
|
|
|
|
const handleSavePrice = () => {
|
|
if (!selectedModel) return;
|
|
const prompt = parseFloat(promptPrice) || 0;
|
|
const completion = parseFloat(completionPrice) || 0;
|
|
const cache = cachePrice.trim() === '' ? prompt : parseFloat(cachePrice) || 0;
|
|
const newPrices = { ...modelPrices, [selectedModel]: { prompt, completion, cache } };
|
|
onPricesChange(newPrices);
|
|
setSelectedModel('');
|
|
setPromptPrice('');
|
|
setCompletionPrice('');
|
|
setCachePrice('');
|
|
};
|
|
|
|
const handleDeletePrice = (model: string) => {
|
|
const newPrices = { ...modelPrices };
|
|
delete newPrices[model];
|
|
onPricesChange(newPrices);
|
|
};
|
|
|
|
const handleEditPrice = (model: string) => {
|
|
const price = modelPrices[model];
|
|
setSelectedModel(model);
|
|
setPromptPrice(price?.prompt?.toString() || '');
|
|
setCompletionPrice(price?.completion?.toString() || '');
|
|
setCachePrice(price?.cache?.toString() || '');
|
|
};
|
|
|
|
const handleModelSelect = (value: string) => {
|
|
setSelectedModel(value);
|
|
const price = modelPrices[value];
|
|
if (price) {
|
|
setPromptPrice(price.prompt.toString());
|
|
setCompletionPrice(price.completion.toString());
|
|
setCachePrice(price.cache.toString());
|
|
} else {
|
|
setPromptPrice('');
|
|
setCompletionPrice('');
|
|
setCachePrice('');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Card title={t('usage_stats.model_price_settings')}>
|
|
<div className={styles.pricingSection}>
|
|
{/* Price Form */}
|
|
<div className={styles.priceForm}>
|
|
<div className={styles.formRow}>
|
|
<div className={styles.formField}>
|
|
<label>{t('usage_stats.model_name')}</label>
|
|
<select
|
|
value={selectedModel}
|
|
onChange={(e) => handleModelSelect(e.target.value)}
|
|
className={styles.select}
|
|
>
|
|
<option value="">{t('usage_stats.model_price_select_placeholder')}</option>
|
|
{modelNames.map((name) => (
|
|
<option key={name} value={name}>
|
|
{name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div className={styles.formField}>
|
|
<label>{t('usage_stats.model_price_prompt')} ($/1M)</label>
|
|
<Input
|
|
type="number"
|
|
value={promptPrice}
|
|
onChange={(e) => setPromptPrice(e.target.value)}
|
|
placeholder="0.00"
|
|
step="0.0001"
|
|
/>
|
|
</div>
|
|
<div className={styles.formField}>
|
|
<label>{t('usage_stats.model_price_completion')} ($/1M)</label>
|
|
<Input
|
|
type="number"
|
|
value={completionPrice}
|
|
onChange={(e) => setCompletionPrice(e.target.value)}
|
|
placeholder="0.00"
|
|
step="0.0001"
|
|
/>
|
|
</div>
|
|
<div className={styles.formField}>
|
|
<label>{t('usage_stats.model_price_cache')} ($/1M)</label>
|
|
<Input
|
|
type="number"
|
|
value={cachePrice}
|
|
onChange={(e) => setCachePrice(e.target.value)}
|
|
placeholder="0.00"
|
|
step="0.0001"
|
|
/>
|
|
</div>
|
|
<Button variant="primary" onClick={handleSavePrice} disabled={!selectedModel}>
|
|
{t('common.save')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Saved Prices List */}
|
|
<div className={styles.pricesList}>
|
|
<h4 className={styles.pricesTitle}>{t('usage_stats.saved_prices')}</h4>
|
|
{Object.keys(modelPrices).length > 0 ? (
|
|
<div className={styles.pricesGrid}>
|
|
{Object.entries(modelPrices).map(([model, price]) => (
|
|
<div key={model} className={styles.priceItem}>
|
|
<div className={styles.priceInfo}>
|
|
<span className={styles.priceModel}>{model}</span>
|
|
<div className={styles.priceMeta}>
|
|
<span>
|
|
{t('usage_stats.model_price_prompt')}: ${price.prompt.toFixed(4)}/1M
|
|
</span>
|
|
<span>
|
|
{t('usage_stats.model_price_completion')}: ${price.completion.toFixed(4)}/1M
|
|
</span>
|
|
<span>
|
|
{t('usage_stats.model_price_cache')}: ${price.cache.toFixed(4)}/1M
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className={styles.priceActions}>
|
|
<Button variant="secondary" size="sm" onClick={() => handleEditPrice(model)}>
|
|
{t('common.edit')}
|
|
</Button>
|
|
<Button variant="danger" size="sm" onClick={() => handleDeletePrice(model)}>
|
|
{t('common.delete')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className={styles.hint}>{t('usage_stats.model_price_empty')}</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
);
|
|
}
|