Merge branch 'gemini-audio-billing' into alpha
This commit is contained in:
@@ -20,7 +20,7 @@ import {
|
||||
renderQuota,
|
||||
stringToColor,
|
||||
getLogOther,
|
||||
renderModelTag
|
||||
renderModelTag,
|
||||
} from '../../helpers';
|
||||
|
||||
import {
|
||||
@@ -40,15 +40,11 @@ import {
|
||||
Typography,
|
||||
Divider,
|
||||
Input,
|
||||
DatePicker
|
||||
DatePicker,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { ITEMS_PER_PAGE } from '../../constants';
|
||||
import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph';
|
||||
import {
|
||||
IconSetting,
|
||||
IconSearch,
|
||||
IconForward
|
||||
} from '@douyinfe/semi-icons';
|
||||
import { IconSetting, IconSearch, IconForward } from '@douyinfe/semi-icons';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@@ -201,8 +197,8 @@ const LogsTable = () => {
|
||||
if (!modelMapped) {
|
||||
return renderModelTag(record.model_name, {
|
||||
onClick: (event) => {
|
||||
copyText(event, record.model_name).then((r) => { });
|
||||
}
|
||||
copyText(event, record.model_name).then((r) => {});
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return (
|
||||
@@ -212,20 +208,26 @@ const LogsTable = () => {
|
||||
content={
|
||||
<div style={{ padding: 10 }}>
|
||||
<Space vertical align={'start'}>
|
||||
<div className="flex items-center">
|
||||
<Text strong style={{ marginRight: 8 }}>{t('请求并计费模型')}:</Text>
|
||||
<div className='flex items-center'>
|
||||
<Text strong style={{ marginRight: 8 }}>
|
||||
{t('请求并计费模型')}:
|
||||
</Text>
|
||||
{renderModelTag(record.model_name, {
|
||||
onClick: (event) => {
|
||||
copyText(event, record.model_name).then((r) => { });
|
||||
}
|
||||
copyText(event, record.model_name).then((r) => {});
|
||||
},
|
||||
})}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Text strong style={{ marginRight: 8 }}>{t('实际模型')}:</Text>
|
||||
<div className='flex items-center'>
|
||||
<Text strong style={{ marginRight: 8 }}>
|
||||
{t('实际模型')}:
|
||||
</Text>
|
||||
{renderModelTag(other.upstream_model_name, {
|
||||
onClick: (event) => {
|
||||
copyText(event, other.upstream_model_name).then((r) => { });
|
||||
}
|
||||
copyText(event, other.upstream_model_name).then(
|
||||
(r) => {},
|
||||
);
|
||||
},
|
||||
})}
|
||||
</div>
|
||||
</Space>
|
||||
@@ -234,9 +236,13 @@ const LogsTable = () => {
|
||||
>
|
||||
{renderModelTag(record.model_name, {
|
||||
onClick: (event) => {
|
||||
copyText(event, record.model_name).then((r) => { });
|
||||
copyText(event, record.model_name).then((r) => {});
|
||||
},
|
||||
suffixIcon: <IconForward style={{ width: '0.9em', height: '0.9em', opacity: 0.75 }} />
|
||||
suffixIcon: (
|
||||
<IconForward
|
||||
style={{ width: '0.9em', height: '0.9em', opacity: 0.75 }}
|
||||
/>
|
||||
),
|
||||
})}
|
||||
</Popover>
|
||||
</Space>
|
||||
@@ -597,21 +603,21 @@ const LogsTable = () => {
|
||||
}
|
||||
let content = other?.claude
|
||||
? renderClaudeModelPriceSimple(
|
||||
other.model_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other.cache_tokens || 0,
|
||||
other.cache_ratio || 1.0,
|
||||
other.cache_creation_tokens || 0,
|
||||
other.cache_creation_ratio || 1.0,
|
||||
)
|
||||
other.model_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other.cache_tokens || 0,
|
||||
other.cache_ratio || 1.0,
|
||||
other.cache_creation_tokens || 0,
|
||||
other.cache_creation_ratio || 1.0,
|
||||
)
|
||||
: renderModelPriceSimple(
|
||||
other.model_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other.cache_tokens || 0,
|
||||
other.cache_ratio || 1.0,
|
||||
);
|
||||
other.model_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other.cache_tokens || 0,
|
||||
other.cache_ratio || 1.0,
|
||||
);
|
||||
return (
|
||||
<Paragraph
|
||||
ellipsis={{
|
||||
@@ -650,25 +656,25 @@ const LogsTable = () => {
|
||||
visible={showColumnSelector}
|
||||
onCancel={() => setShowColumnSelector(false)}
|
||||
footer={
|
||||
<div className="flex justify-end">
|
||||
<div className='flex justify-end'>
|
||||
<Button
|
||||
theme="light"
|
||||
theme='light'
|
||||
onClick={() => initDefaultColumns()}
|
||||
className="!rounded-full"
|
||||
className='!rounded-full'
|
||||
>
|
||||
{t('重置')}
|
||||
</Button>
|
||||
<Button
|
||||
theme="light"
|
||||
theme='light'
|
||||
onClick={() => setShowColumnSelector(false)}
|
||||
className="!rounded-full"
|
||||
className='!rounded-full'
|
||||
>
|
||||
{t('取消')}
|
||||
</Button>
|
||||
<Button
|
||||
type='primary'
|
||||
onClick={() => setShowColumnSelector(false)}
|
||||
className="!rounded-full"
|
||||
className='!rounded-full'
|
||||
>
|
||||
{t('确定')}
|
||||
</Button>
|
||||
@@ -688,7 +694,7 @@ const LogsTable = () => {
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-wrap max-h-96 overflow-y-auto rounded-lg p-4"
|
||||
className='flex flex-wrap max-h-96 overflow-y-auto rounded-lg p-4'
|
||||
style={{ border: '1px solid var(--semi-color-border)' }}
|
||||
>
|
||||
{allColumns.map((column) => {
|
||||
@@ -703,10 +709,7 @@ const LogsTable = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={column.key}
|
||||
className="w-1/2 mb-4 pr-2"
|
||||
>
|
||||
<div key={column.key} className='w-1/2 mb-4 pr-2'>
|
||||
<Checkbox
|
||||
checked={!!visibleColumns[column.key]}
|
||||
onChange={(e) =>
|
||||
@@ -904,27 +907,27 @@ const LogsTable = () => {
|
||||
key: t('日志详情'),
|
||||
value: other?.claude
|
||||
? renderClaudeLogContent(
|
||||
other?.model_ratio,
|
||||
other.completion_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other.cache_ratio || 1.0,
|
||||
other.cache_creation_ratio || 1.0,
|
||||
)
|
||||
other?.model_ratio,
|
||||
other.completion_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other.cache_ratio || 1.0,
|
||||
other.cache_creation_ratio || 1.0,
|
||||
)
|
||||
: renderLogContent(
|
||||
other?.model_ratio,
|
||||
other.completion_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other?.user_group_ratio,
|
||||
false,
|
||||
1.0,
|
||||
undefined,
|
||||
other.web_search || false,
|
||||
other.web_search_call_count || 0,
|
||||
other.file_search || false,
|
||||
other.file_search_call_count || 0,
|
||||
),
|
||||
other?.model_ratio,
|
||||
other.completion_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other?.user_group_ratio,
|
||||
false,
|
||||
1.0,
|
||||
undefined,
|
||||
other.web_search || false,
|
||||
other.web_search_call_count || 0,
|
||||
other.file_search || false,
|
||||
other.file_search_call_count || 0,
|
||||
),
|
||||
});
|
||||
}
|
||||
if (logs[i].type === 2) {
|
||||
@@ -990,6 +993,9 @@ const LogsTable = () => {
|
||||
other?.file_search || false,
|
||||
other?.file_search_call_count || 0,
|
||||
other?.file_search_price || 0,
|
||||
other?.audio_input_seperate_price || false,
|
||||
other?.audio_input_token_count || 0,
|
||||
other?.audio_input_price || 0,
|
||||
);
|
||||
}
|
||||
expandDataLocal.push({
|
||||
@@ -1039,7 +1045,7 @@ const LogsTable = () => {
|
||||
|
||||
const handlePageChange = (page) => {
|
||||
setActivePage(page);
|
||||
loadLogs(page, pageSize, logType).then((r) => { });
|
||||
loadLogs(page, pageSize, logType).then((r) => {});
|
||||
};
|
||||
|
||||
const handlePageSizeChange = async (size) => {
|
||||
@@ -1086,16 +1092,18 @@ const LogsTable = () => {
|
||||
|
||||
// 检查是否有任何记录有展开内容
|
||||
const hasExpandableRows = () => {
|
||||
return logs.some(log => expandData[log.key] && expandData[log.key].length > 0);
|
||||
return logs.some(
|
||||
(log) => expandData[log.key] && expandData[log.key].length > 0,
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderColumnSelector()}
|
||||
<Card
|
||||
className="!rounded-2xl mb-4"
|
||||
className='!rounded-2xl mb-4'
|
||||
title={
|
||||
<div className="flex flex-col w-full">
|
||||
<div className='flex flex-col w-full'>
|
||||
<Spin spinning={loadingStat}>
|
||||
<Space>
|
||||
<Tag
|
||||
@@ -1138,15 +1146,15 @@ const LogsTable = () => {
|
||||
</Space>
|
||||
</Spin>
|
||||
|
||||
<Divider margin="12px" />
|
||||
<Divider margin='12px' />
|
||||
|
||||
{/* 搜索表单区域 */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div className='flex flex-col gap-4'>
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4'>
|
||||
{/* 时间选择器 */}
|
||||
<div className="col-span-1 lg:col-span-2">
|
||||
<div className='col-span-1 lg:col-span-2'>
|
||||
<DatePicker
|
||||
className="w-full"
|
||||
className='w-full'
|
||||
value={[start_timestamp, end_timestamp]}
|
||||
type='dateTimeRange'
|
||||
onChange={(value) => {
|
||||
@@ -1162,7 +1170,7 @@ const LogsTable = () => {
|
||||
<Select
|
||||
value={logType.toString()}
|
||||
placeholder={t('日志类型')}
|
||||
className="!rounded-full"
|
||||
className='!rounded-full'
|
||||
onChange={(value) => {
|
||||
setLogType(parseInt(value));
|
||||
loadLogs(0, pageSize, parseInt(value));
|
||||
@@ -1182,7 +1190,7 @@ const LogsTable = () => {
|
||||
placeholder={t('令牌名称')}
|
||||
value={token_name}
|
||||
onChange={(value) => handleInputChange(value, 'token_name')}
|
||||
className="!rounded-full"
|
||||
className='!rounded-full'
|
||||
showClear
|
||||
/>
|
||||
|
||||
@@ -1191,7 +1199,7 @@ const LogsTable = () => {
|
||||
placeholder={t('模型名称')}
|
||||
value={model_name}
|
||||
onChange={(value) => handleInputChange(value, 'model_name')}
|
||||
className="!rounded-full"
|
||||
className='!rounded-full'
|
||||
showClear
|
||||
/>
|
||||
|
||||
@@ -1200,7 +1208,7 @@ const LogsTable = () => {
|
||||
placeholder={t('分组')}
|
||||
value={group}
|
||||
onChange={(value) => handleInputChange(value, 'group')}
|
||||
className="!rounded-full"
|
||||
className='!rounded-full'
|
||||
showClear
|
||||
/>
|
||||
|
||||
@@ -1211,7 +1219,7 @@ const LogsTable = () => {
|
||||
placeholder={t('渠道 ID')}
|
||||
value={channel}
|
||||
onChange={(value) => handleInputChange(value, 'channel')}
|
||||
className="!rounded-full"
|
||||
className='!rounded-full'
|
||||
showClear
|
||||
/>
|
||||
<Input
|
||||
@@ -1219,7 +1227,7 @@ const LogsTable = () => {
|
||||
placeholder={t('用户名称')}
|
||||
value={username}
|
||||
onChange={(value) => handleInputChange(value, 'username')}
|
||||
className="!rounded-full"
|
||||
className='!rounded-full'
|
||||
showClear
|
||||
/>
|
||||
</>
|
||||
@@ -1227,14 +1235,14 @@ const LogsTable = () => {
|
||||
</div>
|
||||
|
||||
{/* 操作按钮区域 */}
|
||||
<div className="flex justify-between items-center pt-2">
|
||||
<div className='flex justify-between items-center pt-2'>
|
||||
<div></div>
|
||||
<div className="flex gap-2">
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
type='primary'
|
||||
onClick={refresh}
|
||||
loading={loading}
|
||||
className="!rounded-full"
|
||||
className='!rounded-full'
|
||||
>
|
||||
{t('查询')}
|
||||
</Button>
|
||||
@@ -1243,7 +1251,7 @@ const LogsTable = () => {
|
||||
type='tertiary'
|
||||
icon={<IconSetting />}
|
||||
onClick={() => setShowColumnSelector(true)}
|
||||
className="!rounded-full"
|
||||
className='!rounded-full'
|
||||
>
|
||||
{t('列设置')}
|
||||
</Button>
|
||||
@@ -1259,14 +1267,14 @@ const LogsTable = () => {
|
||||
columns={getVisibleColumns()}
|
||||
{...(hasExpandableRows() && {
|
||||
expandedRowRender: expandRowRender,
|
||||
expandRowByClick: true
|
||||
expandRowByClick: true,
|
||||
})}
|
||||
dataSource={logs}
|
||||
rowKey='key'
|
||||
loading={loading}
|
||||
scroll={{ x: 'max-content' }}
|
||||
className="rounded-xl overflow-hidden"
|
||||
size="middle"
|
||||
className='rounded-xl overflow-hidden'
|
||||
size='middle'
|
||||
pagination={{
|
||||
formatPageText: (page) =>
|
||||
t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
|
||||
|
||||
@@ -44,18 +44,18 @@ import {
|
||||
|
||||
// 侧边栏图标颜色映射
|
||||
export const sidebarIconColors = {
|
||||
dashboard: "#4F46E5", // 紫蓝色
|
||||
terminal: "#10B981", // 绿色
|
||||
message: "#06B6D4", // 青色
|
||||
key: "#3B82F6", // 蓝色
|
||||
chart: "#8B5CF6", // 紫色
|
||||
image: "#EC4899", // 粉色
|
||||
check: "#F59E0B", // 琥珀色
|
||||
credit: "#F97316", // 橙色
|
||||
layers: "#EF4444", // 红色
|
||||
gift: "#F43F5E", // 玫红色
|
||||
user: "#6366F1", // 靛蓝色
|
||||
settings: "#6B7280", // 灰色
|
||||
dashboard: '#4F46E5', // 紫蓝色
|
||||
terminal: '#10B981', // 绿色
|
||||
message: '#06B6D4', // 青色
|
||||
key: '#3B82F6', // 蓝色
|
||||
chart: '#8B5CF6', // 紫色
|
||||
image: '#EC4899', // 粉色
|
||||
check: '#F59E0B', // 琥珀色
|
||||
credit: '#F97316', // 橙色
|
||||
layers: '#EF4444', // 红色
|
||||
gift: '#F43F5E', // 玫红色
|
||||
user: '#6366F1', // 靛蓝色
|
||||
settings: '#6B7280', // 灰色
|
||||
};
|
||||
|
||||
// 获取侧边栏Lucide图标组件
|
||||
@@ -71,32 +71,97 @@ export function getLucideIcon(key, selected = false) {
|
||||
// 根据不同的key返回不同的图标
|
||||
switch (key) {
|
||||
case 'detail':
|
||||
return <LayoutDashboard {...commonProps} color={selected ? sidebarIconColors.dashboard : 'currentColor'} />;
|
||||
return (
|
||||
<LayoutDashboard
|
||||
{...commonProps}
|
||||
color={selected ? sidebarIconColors.dashboard : 'currentColor'}
|
||||
/>
|
||||
);
|
||||
case 'playground':
|
||||
return <TerminalSquare {...commonProps} color={selected ? sidebarIconColors.terminal : 'currentColor'} />;
|
||||
return (
|
||||
<TerminalSquare
|
||||
{...commonProps}
|
||||
color={selected ? sidebarIconColors.terminal : 'currentColor'}
|
||||
/>
|
||||
);
|
||||
case 'chat':
|
||||
return <MessageSquare {...commonProps} color={selected ? sidebarIconColors.message : 'currentColor'} />;
|
||||
return (
|
||||
<MessageSquare
|
||||
{...commonProps}
|
||||
color={selected ? sidebarIconColors.message : 'currentColor'}
|
||||
/>
|
||||
);
|
||||
case 'token':
|
||||
return <Key {...commonProps} color={selected ? sidebarIconColors.key : 'currentColor'} />;
|
||||
return (
|
||||
<Key
|
||||
{...commonProps}
|
||||
color={selected ? sidebarIconColors.key : 'currentColor'}
|
||||
/>
|
||||
);
|
||||
case 'log':
|
||||
return <BarChart3 {...commonProps} color={selected ? sidebarIconColors.chart : 'currentColor'} />;
|
||||
return (
|
||||
<BarChart3
|
||||
{...commonProps}
|
||||
color={selected ? sidebarIconColors.chart : 'currentColor'}
|
||||
/>
|
||||
);
|
||||
case 'midjourney':
|
||||
return <ImageIcon {...commonProps} color={selected ? sidebarIconColors.image : 'currentColor'} />;
|
||||
return (
|
||||
<ImageIcon
|
||||
{...commonProps}
|
||||
color={selected ? sidebarIconColors.image : 'currentColor'}
|
||||
/>
|
||||
);
|
||||
case 'task':
|
||||
return <CheckSquare {...commonProps} color={selected ? sidebarIconColors.check : 'currentColor'} />;
|
||||
return (
|
||||
<CheckSquare
|
||||
{...commonProps}
|
||||
color={selected ? sidebarIconColors.check : 'currentColor'}
|
||||
/>
|
||||
);
|
||||
case 'topup':
|
||||
return <CreditCard {...commonProps} color={selected ? sidebarIconColors.credit : 'currentColor'} />;
|
||||
return (
|
||||
<CreditCard
|
||||
{...commonProps}
|
||||
color={selected ? sidebarIconColors.credit : 'currentColor'}
|
||||
/>
|
||||
);
|
||||
case 'channel':
|
||||
return <Layers {...commonProps} color={selected ? sidebarIconColors.layers : 'currentColor'} />;
|
||||
return (
|
||||
<Layers
|
||||
{...commonProps}
|
||||
color={selected ? sidebarIconColors.layers : 'currentColor'}
|
||||
/>
|
||||
);
|
||||
case 'redemption':
|
||||
return <Gift {...commonProps} color={selected ? sidebarIconColors.gift : 'currentColor'} />;
|
||||
return (
|
||||
<Gift
|
||||
{...commonProps}
|
||||
color={selected ? sidebarIconColors.gift : 'currentColor'}
|
||||
/>
|
||||
);
|
||||
case 'user':
|
||||
case 'personal':
|
||||
return <User {...commonProps} color={selected ? sidebarIconColors.user : 'currentColor'} />;
|
||||
return (
|
||||
<User
|
||||
{...commonProps}
|
||||
color={selected ? sidebarIconColors.user : 'currentColor'}
|
||||
/>
|
||||
);
|
||||
case 'setting':
|
||||
return <Settings {...commonProps} color={selected ? sidebarIconColors.settings : 'currentColor'} />;
|
||||
return (
|
||||
<Settings
|
||||
{...commonProps}
|
||||
color={selected ? sidebarIconColors.settings : 'currentColor'}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <CircleUser {...commonProps} color={selected ? sidebarIconColors.user : 'currentColor'} />;
|
||||
return (
|
||||
<CircleUser
|
||||
{...commonProps}
|
||||
color={selected ? sidebarIconColors.user : 'currentColor'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,12 +180,13 @@ export const getModelCategories = (() => {
|
||||
all: {
|
||||
label: t('全部模型'),
|
||||
icon: null,
|
||||
filter: () => true
|
||||
filter: () => true,
|
||||
},
|
||||
openai: {
|
||||
label: 'OpenAI',
|
||||
icon: <OpenAI />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('gpt') ||
|
||||
filter: (model) =>
|
||||
model.model_name.toLowerCase().includes('gpt') ||
|
||||
model.model_name.toLowerCase().includes('dall-e') ||
|
||||
model.model_name.toLowerCase().includes('whisper') ||
|
||||
model.model_name.toLowerCase().includes('tts') ||
|
||||
@@ -131,109 +197,110 @@ export const getModelCategories = (() => {
|
||||
model.model_name.toLowerCase().includes('ada') ||
|
||||
model.model_name.toLowerCase().includes('o1') ||
|
||||
model.model_name.toLowerCase().includes('o3') ||
|
||||
model.model_name.toLowerCase().includes('o4')
|
||||
model.model_name.toLowerCase().includes('o4'),
|
||||
},
|
||||
anthropic: {
|
||||
label: 'Anthropic',
|
||||
icon: <Claude.Color />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('claude')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('claude'),
|
||||
},
|
||||
gemini: {
|
||||
label: 'Gemini',
|
||||
icon: <Gemini.Color />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('gemini')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('gemini'),
|
||||
},
|
||||
moonshot: {
|
||||
label: 'Moonshot',
|
||||
icon: <Moonshot />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('moonshot')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('moonshot'),
|
||||
},
|
||||
zhipu: {
|
||||
label: t('智谱'),
|
||||
icon: <Zhipu.Color />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('chatglm') ||
|
||||
model.model_name.toLowerCase().includes('glm-')
|
||||
filter: (model) =>
|
||||
model.model_name.toLowerCase().includes('chatglm') ||
|
||||
model.model_name.toLowerCase().includes('glm-'),
|
||||
},
|
||||
qwen: {
|
||||
label: t('通义千问'),
|
||||
icon: <Qwen.Color />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('qwen')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('qwen'),
|
||||
},
|
||||
deepseek: {
|
||||
label: 'DeepSeek',
|
||||
icon: <DeepSeek.Color />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('deepseek')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('deepseek'),
|
||||
},
|
||||
minimax: {
|
||||
label: 'MiniMax',
|
||||
icon: <Minimax.Color />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('abab')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('abab'),
|
||||
},
|
||||
baidu: {
|
||||
label: t('文心一言'),
|
||||
icon: <Wenxin.Color />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('ernie')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('ernie'),
|
||||
},
|
||||
xunfei: {
|
||||
label: t('讯飞星火'),
|
||||
icon: <Spark.Color />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('spark')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('spark'),
|
||||
},
|
||||
midjourney: {
|
||||
label: 'Midjourney',
|
||||
icon: <Midjourney />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('mj_')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('mj_'),
|
||||
},
|
||||
tencent: {
|
||||
label: t('腾讯混元'),
|
||||
icon: <Hunyuan.Color />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('hunyuan')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('hunyuan'),
|
||||
},
|
||||
cohere: {
|
||||
label: 'Cohere',
|
||||
icon: <Cohere.Color />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('command')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('command'),
|
||||
},
|
||||
cloudflare: {
|
||||
label: 'Cloudflare',
|
||||
icon: <Cloudflare.Color />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('@cf/')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('@cf/'),
|
||||
},
|
||||
ai360: {
|
||||
label: t('360智脑'),
|
||||
icon: <Ai360.Color />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('360')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('360'),
|
||||
},
|
||||
yi: {
|
||||
label: t('零一万物'),
|
||||
icon: <Yi.Color />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('yi')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('yi'),
|
||||
},
|
||||
jina: {
|
||||
label: 'Jina',
|
||||
icon: <Jina />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('jina')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('jina'),
|
||||
},
|
||||
mistral: {
|
||||
label: 'Mistral AI',
|
||||
icon: <Mistral.Color />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('mistral')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('mistral'),
|
||||
},
|
||||
xai: {
|
||||
label: 'xAI',
|
||||
icon: <XAI />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('grok')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('grok'),
|
||||
},
|
||||
llama: {
|
||||
label: 'Llama',
|
||||
icon: <Ollama />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('llama')
|
||||
filter: (model) => model.model_name.toLowerCase().includes('llama'),
|
||||
},
|
||||
doubao: {
|
||||
label: t('豆包'),
|
||||
icon: <Doubao.Color />,
|
||||
filter: (model) => model.model_name.toLowerCase().includes('doubao')
|
||||
}
|
||||
filter: (model) => model.model_name.toLowerCase().includes('doubao'),
|
||||
},
|
||||
};
|
||||
|
||||
lastLocale = currentLocale;
|
||||
@@ -376,7 +443,13 @@ export function stringToColor(str) {
|
||||
|
||||
// 渲染带有模型图标的标签
|
||||
export function renderModelTag(modelName, options = {}) {
|
||||
const { color, size = 'large', shape = 'circle', onClick, suffixIcon } = options;
|
||||
const {
|
||||
color,
|
||||
size = 'large',
|
||||
shape = 'circle',
|
||||
onClick,
|
||||
suffixIcon,
|
||||
} = options;
|
||||
|
||||
const categories = getModelCategories(i18next.t);
|
||||
let icon = null;
|
||||
@@ -724,6 +797,9 @@ export function renderModelPrice(
|
||||
fileSearch = false,
|
||||
fileSearchCallCount = 0,
|
||||
fileSearchPrice = 0,
|
||||
audioInputSeperatePrice = false,
|
||||
audioInputTokens = 0,
|
||||
audioInputPrice = 0,
|
||||
) {
|
||||
if (modelPrice !== -1) {
|
||||
return i18next.t(
|
||||
@@ -751,9 +827,12 @@ export function renderModelPrice(
|
||||
effectiveInputTokens =
|
||||
inputTokens - imageOutputTokens + imageOutputTokens * imageRatio;
|
||||
}
|
||||
|
||||
if (audioInputTokens > 0) {
|
||||
effectiveInputTokens -= audioInputTokens;
|
||||
}
|
||||
let price =
|
||||
(effectiveInputTokens / 1000000) * inputRatioPrice * groupRatio +
|
||||
(audioInputTokens / 1000000) * audioInputPrice * groupRatio +
|
||||
(completionTokens / 1000000) * completionRatioPrice * groupRatio +
|
||||
(webSearchCallCount / 1000) * webSearchPrice * groupRatio +
|
||||
(fileSearchCallCount / 1000) * fileSearchPrice * groupRatio;
|
||||
@@ -762,8 +841,11 @@ export function renderModelPrice(
|
||||
<>
|
||||
<article>
|
||||
<p>
|
||||
{i18next.t('输入价格:${{price}} / 1M tokens', {
|
||||
{i18next.t('输入价格:${{price}} / 1M tokens{{audioPrice}}', {
|
||||
price: inputRatioPrice,
|
||||
audioPrice: audioInputSeperatePrice
|
||||
? `,音频 $${audioInputPrice} / 1M tokens`
|
||||
: '',
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
@@ -817,96 +899,93 @@ export function renderModelPrice(
|
||||
)}
|
||||
<p></p>
|
||||
<p>
|
||||
{cacheTokens > 0 && !image && !webSearch && !fileSearch
|
||||
? i18next.t(
|
||||
'输入 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
||||
{
|
||||
nonCacheInput: inputTokens - cacheTokens,
|
||||
cacheInput: cacheTokens,
|
||||
cachePrice: inputRatioPrice * cacheRatio,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
ratio: groupRatio,
|
||||
total: price.toFixed(6),
|
||||
},
|
||||
)
|
||||
: image && imageOutputTokens > 0 && !webSearch && !fileSearch
|
||||
? i18next.t(
|
||||
'输入 {{nonImageInput}} tokens + 图片输入 {{imageInput}} tokens * {{imageRatio}} / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
||||
{(() => {
|
||||
// 构建输入部分描述
|
||||
let inputDesc = '';
|
||||
if (image && imageOutputTokens > 0) {
|
||||
inputDesc = i18next.t(
|
||||
'(输入 {{nonImageInput}} tokens + 图片输入 {{imageInput}} tokens * {{imageRatio}} / 1M tokens * ${{price}}',
|
||||
{
|
||||
nonImageInput: inputTokens - imageOutputTokens,
|
||||
imageInput: imageOutputTokens,
|
||||
imageRatio: imageRatio,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
ratio: groupRatio,
|
||||
total: price.toFixed(6),
|
||||
},
|
||||
)
|
||||
: webSearch && webSearchCallCount > 0 && !image && !fileSearch
|
||||
);
|
||||
} else if (cacheTokens > 0) {
|
||||
inputDesc = i18next.t(
|
||||
'(输入 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}}',
|
||||
{
|
||||
nonCacheInput: inputTokens - cacheTokens,
|
||||
cacheInput: cacheTokens,
|
||||
price: inputRatioPrice,
|
||||
cachePrice: cacheRatioPrice,
|
||||
},
|
||||
);
|
||||
} else if (audioInputSeperatePrice && audioInputTokens > 0) {
|
||||
inputDesc = i18next.t(
|
||||
'(输入 {{nonAudioInput}} tokens / 1M tokens * ${{price}} + 音频输入 {{audioInput}} tokens / 1M tokens * ${{audioPrice}}',
|
||||
{
|
||||
nonAudioInput: inputTokens - audioInputTokens,
|
||||
audioInput: audioInputTokens,
|
||||
price: inputRatioPrice,
|
||||
audioPrice: audioInputPrice,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
inputDesc = i18next.t(
|
||||
'(输入 {{input}} tokens / 1M tokens * ${{price}}',
|
||||
{
|
||||
input: inputTokens,
|
||||
price: inputRatioPrice,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 构建输出部分描述
|
||||
const outputDesc = i18next.t(
|
||||
'输出 {{completion}} tokens / 1M tokens * ${{compPrice}}) * 分组倍率 {{ratio}}',
|
||||
{
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
ratio: groupRatio,
|
||||
},
|
||||
);
|
||||
|
||||
// 构建额外服务描述
|
||||
const extraServices = [
|
||||
webSearch && webSearchCallCount > 0
|
||||
? i18next.t(
|
||||
'输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} + Web搜索 {{webSearchCallCount}}次 / 1K 次 * ${{webSearchPrice}} * {{ratio}} = ${{total}}',
|
||||
{
|
||||
input: inputTokens,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
ratio: groupRatio,
|
||||
webSearchCallCount,
|
||||
webSearchPrice,
|
||||
total: price.toFixed(6),
|
||||
},
|
||||
)
|
||||
: fileSearch &&
|
||||
fileSearchCallCount > 0 &&
|
||||
!image &&
|
||||
!webSearch
|
||||
? i18next.t(
|
||||
'输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} + 文件搜索 {{fileSearchCallCount}}次 / 1K 次 * ${{fileSearchPrice}} * {{ratio}}= ${{total}}',
|
||||
' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * 分组倍率 {{ratio}}',
|
||||
{
|
||||
input: inputTokens,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
count: webSearchCallCount,
|
||||
price: webSearchPrice,
|
||||
ratio: groupRatio,
|
||||
fileSearchCallCount,
|
||||
fileSearchPrice,
|
||||
total: price.toFixed(6),
|
||||
},
|
||||
)
|
||||
: webSearch &&
|
||||
webSearchCallCount > 0 &&
|
||||
fileSearch &&
|
||||
fileSearchCallCount > 0 &&
|
||||
!image
|
||||
? i18next.t(
|
||||
'输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} + Web搜索 {{webSearchCallCount}}次 / 1K 次 * ${{webSearchPrice}} * {{ratio}}+ 文件搜索 {{fileSearchCallCount}}次 / 1K 次 * ${{fileSearchPrice}} * {{ratio}}= ${{total}}',
|
||||
{
|
||||
input: inputTokens,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
ratio: groupRatio,
|
||||
webSearchCallCount,
|
||||
webSearchPrice,
|
||||
fileSearchCallCount,
|
||||
fileSearchPrice,
|
||||
total: price.toFixed(6),
|
||||
},
|
||||
)
|
||||
: i18next.t(
|
||||
'输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
||||
{
|
||||
input: inputTokens,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
ratio: groupRatio,
|
||||
total: price.toFixed(6),
|
||||
},
|
||||
)}
|
||||
: '',
|
||||
fileSearch && fileSearchCallCount > 0
|
||||
? i18next.t(
|
||||
' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * 分组倍率 {{ratio}}',
|
||||
{
|
||||
count: fileSearchCallCount,
|
||||
price: fileSearchPrice,
|
||||
ratio: groupRatio,
|
||||
},
|
||||
)
|
||||
: '',
|
||||
].join('');
|
||||
|
||||
return i18next.t(
|
||||
'{{inputDesc}} + {{outputDesc}}{{extraServices}} = ${{total}}',
|
||||
{
|
||||
inputDesc,
|
||||
outputDesc,
|
||||
extraServices,
|
||||
total: price.toFixed(6),
|
||||
},
|
||||
);
|
||||
})()}
|
||||
</p>
|
||||
<p>{i18next.t('仅供参考,以实际扣费为准')}</p>
|
||||
</article>
|
||||
@@ -1077,10 +1156,10 @@ export function renderAudioModelPrice(
|
||||
let audioPrice =
|
||||
(audioInputTokens / 1000000) * inputRatioPrice * audioRatio * groupRatio +
|
||||
(audioCompletionTokens / 1000000) *
|
||||
inputRatioPrice *
|
||||
audioRatio *
|
||||
audioCompletionRatio *
|
||||
groupRatio;
|
||||
inputRatioPrice *
|
||||
audioRatio *
|
||||
audioCompletionRatio *
|
||||
groupRatio;
|
||||
let price = textPrice + audioPrice;
|
||||
return (
|
||||
<>
|
||||
@@ -1136,27 +1215,27 @@ export function renderAudioModelPrice(
|
||||
<p>
|
||||
{cacheTokens > 0
|
||||
? i18next.t(
|
||||
'文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
|
||||
{
|
||||
nonCacheInput: inputTokens - cacheTokens,
|
||||
cacheInput: cacheTokens,
|
||||
cachePrice: inputRatioPrice * cacheRatio,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
total: textPrice.toFixed(6),
|
||||
},
|
||||
)
|
||||
'文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
|
||||
{
|
||||
nonCacheInput: inputTokens - cacheTokens,
|
||||
cacheInput: cacheTokens,
|
||||
cachePrice: inputRatioPrice * cacheRatio,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
total: textPrice.toFixed(6),
|
||||
},
|
||||
)
|
||||
: i18next.t(
|
||||
'文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
|
||||
{
|
||||
input: inputTokens,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
total: textPrice.toFixed(6),
|
||||
},
|
||||
)}
|
||||
'文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
|
||||
{
|
||||
input: inputTokens,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
total: textPrice.toFixed(6),
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{i18next.t(
|
||||
@@ -1293,33 +1372,33 @@ export function renderClaudeModelPrice(
|
||||
<p>
|
||||
{cacheTokens > 0 || cacheCreationTokens > 0
|
||||
? i18next.t(
|
||||
'提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
||||
{
|
||||
nonCacheInput: nonCachedTokens,
|
||||
cacheInput: cacheTokens,
|
||||
cacheRatio: cacheRatio,
|
||||
cacheCreationInput: cacheCreationTokens,
|
||||
cacheCreationRatio: cacheCreationRatio,
|
||||
cachePrice: cacheRatioPrice,
|
||||
cacheCreationPrice: cacheCreationRatioPrice,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
ratio: groupRatio,
|
||||
total: price.toFixed(6),
|
||||
},
|
||||
)
|
||||
'提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
||||
{
|
||||
nonCacheInput: nonCachedTokens,
|
||||
cacheInput: cacheTokens,
|
||||
cacheRatio: cacheRatio,
|
||||
cacheCreationInput: cacheCreationTokens,
|
||||
cacheCreationRatio: cacheCreationRatio,
|
||||
cachePrice: cacheRatioPrice,
|
||||
cacheCreationPrice: cacheCreationRatioPrice,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
ratio: groupRatio,
|
||||
total: price.toFixed(6),
|
||||
},
|
||||
)
|
||||
: i18next.t(
|
||||
'提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
||||
{
|
||||
input: inputTokens,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
ratio: groupRatio,
|
||||
total: price.toFixed(6),
|
||||
},
|
||||
)}
|
||||
'提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
||||
{
|
||||
input: inputTokens,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
ratio: groupRatio,
|
||||
total: price.toFixed(6),
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
<p>{i18next.t('仅供参考,以实际扣费为准')}</p>
|
||||
</article>
|
||||
@@ -1410,7 +1489,9 @@ export function rehypeSplitWordsIntoSpans(options = {}) {
|
||||
|
||||
visit(tree, 'element', (node) => {
|
||||
if (
|
||||
['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'strong'].includes(node.tagName) &&
|
||||
['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'strong'].includes(
|
||||
node.tagName,
|
||||
) &&
|
||||
node.children
|
||||
) {
|
||||
const newChildren = [];
|
||||
@@ -1418,7 +1499,9 @@ export function rehypeSplitWordsIntoSpans(options = {}) {
|
||||
if (child.type === 'text') {
|
||||
try {
|
||||
// 使用 Intl.Segmenter 精准拆分中英文及标点
|
||||
const segmenter = new Intl.Segmenter('zh', { granularity: 'word' });
|
||||
const segmenter = new Intl.Segmenter('zh', {
|
||||
granularity: 'word',
|
||||
});
|
||||
const segments = segmenter.segment(child.value);
|
||||
|
||||
Array.from(segments)
|
||||
@@ -1472,4 +1555,4 @@ export function rehypeSplitWordsIntoSpans(options = {}) {
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user