🎨 chore(web): apply ESLint and Prettier auto-fixes (baseline)

- Ran: bun run eslint:fix && bun run lint:fix
- Inserted AGPL license header via eslint-plugin-header
- Enforced no-multiple-empty-lines and other lint rules
- Formatted code using Prettier v3 (@so1ve/prettier-config)
- No functional changes; formatting-only baseline across JS/JSX files
This commit is contained in:
t0ng7u
2025-08-30 21:15:10 +08:00
parent 41cf516ec5
commit 0d57b1acd4
274 changed files with 11025 additions and 7659 deletions

View File

@@ -17,7 +17,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { getUserIdFromLocalStorage, showError, formatMessageForAPI, isValidMessage } from './utils';
import {
getUserIdFromLocalStorage,
showError,
formatMessageForAPI,
isValidMessage,
} from './utils';
import axios from 'axios';
import { MESSAGE_ROLES } from '../constants/playground.constants';
@@ -90,7 +95,12 @@ API.interceptors.response.use(
// playground
// 构建API请求负载
export const buildApiPayload = (messages, systemPrompt, inputs, parameterEnabled) => {
export const buildApiPayload = (
messages,
systemPrompt,
inputs,
parameterEnabled,
) => {
const processedMessages = messages
.filter(isValidMessage)
.map(formatMessageForAPI)
@@ -100,7 +110,7 @@ export const buildApiPayload = (messages, systemPrompt, inputs, parameterEnabled
if (systemPrompt && systemPrompt.trim()) {
processedMessages.unshift({
role: MESSAGE_ROLES.SYSTEM,
content: systemPrompt.trim()
content: systemPrompt.trim(),
});
}
@@ -119,11 +129,15 @@ export const buildApiPayload = (messages, systemPrompt, inputs, parameterEnabled
max_tokens: 'max_tokens',
frequency_penalty: 'frequency_penalty',
presence_penalty: 'presence_penalty',
seed: 'seed'
seed: 'seed',
};
Object.entries(parameterMappings).forEach(([key, param]) => {
if (parameterEnabled[key] && inputs[param] !== undefined && inputs[param] !== null) {
if (
parameterEnabled[key] &&
inputs[param] !== undefined &&
inputs[param] !== null
) {
payload[param] = inputs[param];
}
});
@@ -136,7 +150,7 @@ export const handleApiError = (error, response = null) => {
const errorInfo = {
error: error.message || '未知错误',
timestamp: new Date().toISOString(),
stack: error.stack
stack: error.stack,
};
if (response) {
@@ -155,15 +169,18 @@ export const handleApiError = (error, response = null) => {
// 处理模型数据
export const processModelsData = (data, currentModel) => {
const modelOptions = data.map(model => ({
const modelOptions = data.map((model) => ({
label: model,
value: model,
}));
const hasCurrentModel = modelOptions.some(option => option.value === currentModel);
const selectedModel = hasCurrentModel && modelOptions.length > 0
? currentModel
: modelOptions[0]?.value;
const hasCurrentModel = modelOptions.some(
(option) => option.value === currentModel,
);
const selectedModel =
hasCurrentModel && modelOptions.length > 0
? currentModel
: modelOptions[0]?.value;
return { modelOptions, selectedModel };
};
@@ -171,20 +188,23 @@ export const processModelsData = (data, currentModel) => {
// 处理分组数据
export const processGroupsData = (data, userGroup) => {
let groupOptions = Object.entries(data).map(([group, info]) => ({
label: info.desc.length > 20 ? info.desc.substring(0, 20) + '...' : info.desc,
label:
info.desc.length > 20 ? info.desc.substring(0, 20) + '...' : info.desc,
value: group,
ratio: info.ratio,
fullLabel: info.desc,
}));
if (groupOptions.length === 0) {
groupOptions = [{
label: '用户分组',
value: '',
ratio: 1,
}];
groupOptions = [
{
label: '用户分组',
value: '',
ratio: 1,
},
];
} else if (userGroup) {
const userGroupIndex = groupOptions.findIndex(g => g.value === userGroup);
const userGroupIndex = groupOptions.findIndex((g) => g.value === userGroup);
if (userGroupIndex > -1) {
const userGroupOption = groupOptions.splice(userGroupIndex, 1)[0];
groupOptions.unshift(userGroupOption);

View File

@@ -36,7 +36,7 @@ export const AuthRedirect = ({ children }) => {
const user = localStorage.getItem('user');
if (user) {
return <Navigate to="/console" replace />;
return <Navigate to='/console' replace />;
}
return children;

View File

@@ -26,4 +26,4 @@ export const toBoolean = (value) => {
return v === 'true' || v === '1';
}
return false;
};
};

View File

@@ -19,9 +19,22 @@ For commercial licensing, please contact support@quantumnous.com
import React from 'react';
import { Progress, Divider, Empty } from '@douyinfe/semi-ui';
import { IllustrationConstruction, IllustrationConstructionDark } from '@douyinfe/semi-illustrations';
import { timestamp2string, timestamp2string1, copy, showSuccess } from './utils';
import { STORAGE_KEYS, DEFAULT_TIME_INTERVALS, DEFAULTS, ILLUSTRATION_SIZE } from '../constants/dashboard.constants';
import {
IllustrationConstruction,
IllustrationConstructionDark,
} from '@douyinfe/semi-illustrations';
import {
timestamp2string,
timestamp2string1,
copy,
showSuccess,
} from './utils';
import {
STORAGE_KEYS,
DEFAULT_TIME_INTERVALS,
DEFAULTS,
ILLUSTRATION_SIZE,
} from '../constants/dashboard.constants';
// ========== 时间相关工具函数 ==========
export const getDefaultTime = () => {
@@ -29,7 +42,8 @@ export const getDefaultTime = () => {
};
export const getTimeInterval = (timeType, isSeconds = false) => {
const intervals = DEFAULT_TIME_INTERVALS[timeType] || DEFAULT_TIME_INTERVALS.hour;
const intervals =
DEFAULT_TIME_INTERVALS[timeType] || DEFAULT_TIME_INTERVALS.hour;
return isSeconds ? intervals.seconds : intervals.minutes;
};
@@ -56,7 +70,7 @@ export const updateMapValue = (map, key, value) => {
};
export const initializeMaps = (key, ...maps) => {
maps.forEach(map => {
maps.forEach((map) => {
if (!map.has(key)) {
map.set(key, 0);
}
@@ -64,8 +78,14 @@ export const initializeMaps = (key, ...maps) => {
};
// ========== 图表相关工具函数 ==========
export const updateChartSpec = (setterFunc, newData, subtitle, newColors, dataId) => {
setterFunc(prev => ({
export const updateChartSpec = (
setterFunc,
newData,
subtitle,
newColors,
dataId,
) => {
setterFunc((prev) => ({
...prev,
data: [{ id: dataId, values: newData }],
title: {
@@ -88,12 +108,12 @@ export const getTrendSpec = (data, color) => ({
axes: [
{
orient: 'bottom',
visible: false
visible: false,
},
{
orient: 'left',
visible: false
}
visible: false,
},
],
padding: 0,
autoFit: false,
@@ -103,20 +123,20 @@ export const getTrendSpec = (data, color) => ({
line: {
style: {
stroke: color,
lineWidth: 2
}
lineWidth: 2,
},
},
point: {
visible: false
visible: false,
},
background: {
fill: 'transparent'
}
fill: 'transparent',
},
});
// ========== UI 工具函数 ==========
export const createSectionTitle = (Icon, text) => (
<div className="flex items-center gap-2">
<div className='flex items-center gap-2'>
<Icon size={16} />
{text}
</div>
@@ -147,13 +167,20 @@ export const getUptimeStatusText = (status, uptimeStatusMap, t) =>
uptimeStatusMap[status]?.text || t('未知');
// ========== 监控列表渲染函数 ==========
export const renderMonitorList = (monitors, getUptimeStatusColor, getUptimeStatusText, t) => {
export const renderMonitorList = (
monitors,
getUptimeStatusColor,
getUptimeStatusText,
t,
) => {
if (!monitors || monitors.length === 0) {
return (
<div className="flex justify-center items-center py-4">
<div className='flex justify-center items-center py-4'>
<Empty
image={<IllustrationConstruction style={ILLUSTRATION_SIZE} />}
darkModeImage={<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />}
darkModeImage={
<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />
}
title={t('暂无监控数据')}
/>
</div>
@@ -168,20 +195,26 @@ export const renderMonitorList = (monitors, getUptimeStatusColor, getUptimeStatu
});
const renderItem = (monitor, idx) => (
<div key={idx} className="p-2 hover:bg-white rounded-lg transition-colors">
<div className="flex items-center justify-between mb-1">
<div className="flex items-center gap-2">
<div key={idx} className='p-2 hover:bg-white rounded-lg transition-colors'>
<div className='flex items-center justify-between mb-1'>
<div className='flex items-center gap-2'>
<div
className="w-2 h-2 rounded-full flex-shrink-0"
className='w-2 h-2 rounded-full flex-shrink-0'
style={{ backgroundColor: getUptimeStatusColor(monitor.status) }}
/>
<span className="text-sm font-medium text-gray-900">{monitor.name}</span>
<span className='text-sm font-medium text-gray-900'>
{monitor.name}
</span>
</div>
<span className="text-xs text-gray-500">{((monitor.uptime || 0) * 100).toFixed(2)}%</span>
<span className='text-xs text-gray-500'>
{((monitor.uptime || 0) * 100).toFixed(2)}%
</span>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500">{getUptimeStatusText(monitor.status)}</span>
<div className="flex-1">
<div className='flex items-center gap-2'>
<span className='text-xs text-gray-500'>
{getUptimeStatusText(monitor.status)}
</span>
<div className='flex-1'>
<Progress
percent={(monitor.uptime || 0) * 100}
showInfo={false}
@@ -194,10 +227,10 @@ export const renderMonitorList = (monitors, getUptimeStatusColor, getUptimeStatu
);
return Object.entries(grouped).map(([gname, list]) => (
<div key={gname || 'default'} className="mb-2">
<div key={gname || 'default'} className='mb-2'>
{gname && (
<>
<div className="text-md font-semibold text-gray-500 px-2 py-1">
<div className='text-md font-semibold text-gray-500 px-2 py-1'>
{gname}
</div>
<Divider />
@@ -209,7 +242,12 @@ export const renderMonitorList = (monitors, getUptimeStatusColor, getUptimeStatu
};
// ========== 数据处理函数 ==========
export const processRawData = (data, dataExportDefaultTime, initializeMaps, updateMapValue) => {
export const processRawData = (
data,
dataExportDefaultTime,
initializeMaps,
updateMapValue,
) => {
const result = {
totalQuota: 0,
totalTimes: 0,
@@ -218,7 +256,7 @@ export const processRawData = (data, dataExportDefaultTime, initializeMaps, upda
timePoints: [],
timeQuotaMap: new Map(),
timeTokensMap: new Map(),
timeCountMap: new Map()
timeCountMap: new Map(),
};
data.forEach((item) => {
@@ -232,7 +270,12 @@ export const processRawData = (data, dataExportDefaultTime, initializeMaps, upda
result.timePoints.push(timeKey);
}
initializeMaps(timeKey, result.timeQuotaMap, result.timeTokensMap, result.timeCountMap);
initializeMaps(
timeKey,
result.timeQuotaMap,
result.timeTokensMap,
result.timeCountMap,
);
updateMapValue(result.timeQuotaMap, timeKey, item.quota);
updateMapValue(result.timeTokensMap, timeKey, item.token_used);
updateMapValue(result.timeCountMap, timeKey, item.count);
@@ -242,10 +285,16 @@ export const processRawData = (data, dataExportDefaultTime, initializeMaps, upda
return result;
};
export const calculateTrendData = (timePoints, timeQuotaMap, timeTokensMap, timeCountMap, dataExportDefaultTime) => {
const quotaTrend = timePoints.map(time => timeQuotaMap.get(time) || 0);
const tokensTrend = timePoints.map(time => timeTokensMap.get(time) || 0);
const countTrend = timePoints.map(time => timeCountMap.get(time) || 0);
export const calculateTrendData = (
timePoints,
timeQuotaMap,
timeTokensMap,
timeCountMap,
dataExportDefaultTime,
) => {
const quotaTrend = timePoints.map((time) => timeQuotaMap.get(time) || 0);
const tokensTrend = timePoints.map((time) => timeTokensMap.get(time) || 0);
const countTrend = timePoints.map((time) => timeCountMap.get(time) || 0);
const rpmTrend = [];
const tpmTrend = [];
@@ -267,7 +316,7 @@ export const calculateTrendData = (timePoints, timeQuotaMap, timeTokensMap, time
consumeQuota: quotaTrend,
tokens: tokensTrend,
rpm: rpmTrend,
tpm: tpmTrend
tpm: tpmTrend,
};
};
@@ -296,7 +345,11 @@ export const aggregateDataByTimeAndModel = (data, dataExportDefaultTime) => {
return aggregatedData;
};
export const generateChartTimePoints = (aggregatedData, data, dataExportDefaultTime) => {
export const generateChartTimePoints = (
aggregatedData,
data,
dataExportDefaultTime,
) => {
let chartTimePoints = Array.from(
new Set([...aggregatedData.values()].map((d) => d.time)),
);
@@ -305,10 +358,12 @@ export const generateChartTimePoints = (aggregatedData, data, dataExportDefaultT
const lastTime = Math.max(...data.map((item) => item.created_at));
const interval = getTimeInterval(dataExportDefaultTime, true);
chartTimePoints = Array.from({ length: DEFAULTS.MAX_TREND_POINTS }, (_, i) =>
timestamp2string1(lastTime - (6 - i) * interval, dataExportDefaultTime),
chartTimePoints = Array.from(
{ length: DEFAULTS.MAX_TREND_POINTS },
(_, i) =>
timestamp2string1(lastTime - (6 - i) * interval, dataExportDefaultTime),
);
}
return chartTimePoints;
};
};

View File

@@ -23,4 +23,4 @@ export function getLogOther(otherStr) {
}
let other = JSON.parse(otherStr);
return other;
}
}

View File

@@ -88,104 +88,34 @@ export function getLucideIcon(key, selected = false) {
// 根据不同的key返回不同的图标
switch (key) {
case 'detail':
return (
<LayoutDashboard
{...commonProps}
color={iconColor}
/>
);
return <LayoutDashboard {...commonProps} color={iconColor} />;
case 'playground':
return (
<TerminalSquare
{...commonProps}
color={iconColor}
/>
);
return <TerminalSquare {...commonProps} color={iconColor} />;
case 'chat':
return (
<MessageSquare
{...commonProps}
color={iconColor}
/>
);
return <MessageSquare {...commonProps} color={iconColor} />;
case 'token':
return (
<Key
{...commonProps}
color={iconColor}
/>
);
return <Key {...commonProps} color={iconColor} />;
case 'log':
return (
<BarChart3
{...commonProps}
color={iconColor}
/>
);
return <BarChart3 {...commonProps} color={iconColor} />;
case 'midjourney':
return (
<ImageIcon
{...commonProps}
color={iconColor}
/>
);
return <ImageIcon {...commonProps} color={iconColor} />;
case 'task':
return (
<CheckSquare
{...commonProps}
color={iconColor}
/>
);
return <CheckSquare {...commonProps} color={iconColor} />;
case 'topup':
return (
<CreditCard
{...commonProps}
color={iconColor}
/>
);
return <CreditCard {...commonProps} color={iconColor} />;
case 'channel':
return (
<Layers
{...commonProps}
color={iconColor}
/>
);
return <Layers {...commonProps} color={iconColor} />;
case 'redemption':
return (
<Gift
{...commonProps}
color={iconColor}
/>
);
return <Gift {...commonProps} color={iconColor} />;
case 'user':
case 'personal':
return (
<User
{...commonProps}
color={iconColor}
/>
);
return <User {...commonProps} color={iconColor} />;
case 'models':
return (
<Package
{...commonProps}
color={iconColor}
/>
);
return <Package {...commonProps} color={iconColor} />;
case 'setting':
return (
<Settings
{...commonProps}
color={iconColor}
/>
);
return <Settings {...commonProps} color={iconColor} />;
default:
return (
<CircleUser
{...commonProps}
color={iconColor}
/>
);
return <CircleUser {...commonProps} color={iconColor} />;
}
}
@@ -431,7 +361,7 @@ export function getLobeHubIcon(iconName, size = 14) {
if (typeof iconName === 'string') iconName = iconName.trim();
// 如果没有图标名称,返回 Avatar
if (!iconName) {
return <Avatar size="extra-extra-small">?</Avatar>;
return <Avatar size='extra-extra-small'>?</Avatar>;
}
// 解析组件路径与点号链式属性
@@ -451,9 +381,12 @@ export function getLobeHubIcon(iconName, size = 14) {
}
// 失败兜底
if (!IconComponent || (typeof IconComponent !== 'function' && typeof IconComponent !== 'object')) {
if (
!IconComponent ||
(typeof IconComponent !== 'function' && typeof IconComponent !== 'object')
) {
const firstLetter = String(iconName).charAt(0).toUpperCase();
return <Avatar size="extra-extra-small">{firstLetter}</Avatar>;
return <Avatar size='extra-extra-small'>{firstLetter}</Avatar>;
}
// 解析点号链式属性形如key={...}、key='...'、key="..."、key=123、key、key=true/false
@@ -467,7 +400,10 @@ export function getLobeHubIcon(iconName, size = 14) {
v = v.slice(1, -1).trim();
}
// 去除引号
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
if (
(v.startsWith('"') && v.endsWith('"')) ||
(v.startsWith("'") && v.endsWith("'"))
) {
return v.slice(1, -1);
}
// 布尔
@@ -765,7 +701,9 @@ const measureTextWidth = (
};
export function truncateText(text, maxWidth = 200) {
const isMobileScreen = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`).matches;
const isMobileScreen = window.matchMedia(
`(max-width: ${MOBILE_BREAKPOINT - 1}px)`,
).matches;
if (!isMobileScreen) {
return text;
}
@@ -959,7 +897,6 @@ export function renderQuotaWithAmount(amount) {
}
export function renderQuota(quota, digits = 2) {
let quotaPerUnit = localStorage.getItem('quota_per_unit');
let displayInCurrency = localStorage.getItem('display_in_currency');
quotaPerUnit = parseFloat(quotaPerUnit);
@@ -986,7 +923,7 @@ function isValidGroupRatio(ratio) {
/**
* Helper function to get effective ratio and label
* @param {number} groupRatio - The default group ratio
* @param {number} user_group_ratio - The user-specific group ratio
* @param {number} user_group_ratio - The user-specific group ratio
* @returns {Object} - Object containing { ratio, label, useUserGroupRatio }
*/
function getEffectiveRatio(groupRatio, user_group_ratio) {
@@ -999,7 +936,7 @@ function getEffectiveRatio(groupRatio, user_group_ratio) {
return {
ratio: effectiveRatio,
label: ratioLabel,
useUserGroupRatio: useUserGroupRatio
useUserGroupRatio: useUserGroupRatio,
};
}
@@ -1015,7 +952,7 @@ function renderPriceSimpleCore({
cacheCreationRatio = 1.0,
image = false,
imageRatio = 1.0,
isSystemPromptOverride = false
isSystemPromptOverride = false,
}) {
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
groupRatio,
@@ -1059,7 +996,7 @@ function renderPriceSimpleCore({
cacheRatio: cacheRatio,
cacheCreationRatio: cacheCreationRatio,
imageRatio: imageRatio,
})
});
if (isSystemPromptOverride) {
result += '\n\r' + i18next.t('系统提示覆盖');
@@ -1091,7 +1028,10 @@ export function renderModelPrice(
audioInputTokens = 0,
audioInputPrice = 0,
) {
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(groupRatio, user_group_ratio);
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
groupRatio,
user_group_ratio,
);
groupRatio = effectiveGroupRatio;
if (modelPrice !== -1) {
@@ -1251,25 +1191,25 @@ export function renderModelPrice(
const extraServices = [
webSearch && webSearchCallCount > 0
? i18next.t(
' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}',
{
count: webSearchCallCount,
price: webSearchPrice,
ratio: groupRatio,
ratioType: ratioLabel,
},
)
' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}',
{
count: webSearchCallCount,
price: webSearchPrice,
ratio: groupRatio,
ratioType: ratioLabel,
},
)
: '',
fileSearch && fileSearchCallCount > 0
? i18next.t(
' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}',
{
count: fileSearchCallCount,
price: fileSearchPrice,
ratio: groupRatio,
ratioType: ratioLabel,
},
)
' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}',
{
count: fileSearchCallCount,
price: fileSearchPrice,
ratio: groupRatio,
ratioType: ratioLabel,
},
)
: '',
].join('');
@@ -1305,7 +1245,11 @@ export function renderLogContent(
fileSearch = false,
fileSearchCallCount = 0,
) {
const { ratio, label: ratioLabel, useUserGroupRatio: useUserGroupRatio } = getEffectiveRatio(groupRatio, user_group_ratio);
const {
ratio,
label: ratioLabel,
useUserGroupRatio: useUserGroupRatio,
} = getEffectiveRatio(groupRatio, user_group_ratio);
if (modelPrice !== -1) {
return i18next.t('模型价格 ${{price}}{{ratioType}} {{ratio}}', {
@@ -1378,7 +1322,7 @@ export function renderModelPriceSimple(
cacheCreationRatio,
image,
imageRatio,
isSystemPromptOverride
isSystemPromptOverride,
});
}
@@ -1397,7 +1341,10 @@ export function renderAudioModelPrice(
cacheTokens = 0,
cacheRatio = 1.0,
) {
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(groupRatio, user_group_ratio);
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
groupRatio,
user_group_ratio,
);
groupRatio = effectiveGroupRatio;
// 1 ratio = $0.002 / 1K tokens
if (modelPrice !== -1) {
@@ -1432,10 +1379,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 (
<>
@@ -1491,27 +1438,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(
@@ -1547,9 +1494,7 @@ export function renderQuotaWithPrompt(quota, digits) {
let displayInCurrency = localStorage.getItem('display_in_currency');
displayInCurrency = displayInCurrency === 'true';
if (displayInCurrency) {
return (
i18next.t('等价金额:') + renderQuota(quota, digits)
);
return i18next.t('等价金额:') + renderQuota(quota, digits);
}
return '';
}
@@ -1567,7 +1512,10 @@ export function renderClaudeModelPrice(
cacheCreationTokens = 0,
cacheCreationRatio = 1.0,
) {
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(groupRatio, user_group_ratio);
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
groupRatio,
user_group_ratio,
);
groupRatio = effectiveGroupRatio;
if (modelPrice !== -1) {
@@ -1650,35 +1598,35 @@ 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}} * {{ratioType}} {{ratio}} = ${{total}}',
{
nonCacheInput: nonCachedTokens,
cacheInput: cacheTokens,
cacheRatio: cacheRatio,
cacheCreationInput: cacheCreationTokens,
cacheCreationRatio: cacheCreationRatio,
cachePrice: cacheRatioPrice,
cacheCreationPrice: cacheCreationRatioPrice,
price: inputRatioPrice,
completion: completionTokens,
compPrice: completionRatioPrice,
ratio: groupRatio,
ratioType: ratioLabel,
total: price.toFixed(6),
},
)
'提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}',
{
nonCacheInput: nonCachedTokens,
cacheInput: cacheTokens,
cacheRatio: cacheRatio,
cacheCreationInput: cacheCreationTokens,
cacheCreationRatio: cacheCreationRatio,
cachePrice: cacheRatioPrice,
cacheCreationPrice: cacheCreationRatioPrice,
price: inputRatioPrice,
completion: completionTokens,
compPrice: completionRatioPrice,
ratio: groupRatio,
ratioType: ratioLabel,
total: price.toFixed(6),
},
)
: i18next.t(
'提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}',
{
input: inputTokens,
price: inputRatioPrice,
completion: completionTokens,
compPrice: completionRatioPrice,
ratio: groupRatio,
ratioType: ratioLabel,
total: price.toFixed(6),
},
)}
'提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}',
{
input: inputTokens,
price: inputRatioPrice,
completion: completionTokens,
compPrice: completionRatioPrice,
ratio: groupRatio,
ratioType: ratioLabel,
total: price.toFixed(6),
},
)}
</p>
<p>{i18next.t('仅供参考,以实际扣费为准')}</p>
</article>
@@ -1696,7 +1644,10 @@ export function renderClaudeLogContent(
cacheRatio = 1.0,
cacheCreationRatio = 1.0,
) {
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(groupRatio, user_group_ratio);
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
groupRatio,
user_group_ratio,
);
groupRatio = effectiveGroupRatio;
if (modelPrice !== -1) {

View File

@@ -60,4 +60,4 @@ export function getServerAddress() {
}
return serverAddress;
}
}

View File

@@ -21,7 +21,10 @@ import { Toast, Pagination } from '@douyinfe/semi-ui';
import { toastConstants } from '../constants';
import React from 'react';
import { toast } from 'react-toastify';
import { THINK_TAG_REGEX, MESSAGE_ROLES } from '../constants/playground.constants';
import {
THINK_TAG_REGEX,
MESSAGE_ROLES,
} from '../constants/playground.constants';
import { TABLE_COMPACT_MODES_KEY } from '../constants';
import { MOBILE_BREAKPOINT } from '../hooks/common/useIsMobile';
@@ -95,7 +98,9 @@ let showSuccessOptions = { autoClose: toastConstants.SUCCESS_TIMEOUT };
let showInfoOptions = { autoClose: toastConstants.INFO_TIMEOUT };
let showNoticeOptions = { autoClose: false };
const isMobileScreen = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`).matches;
const isMobileScreen = window.matchMedia(
`(max-width: ${MOBILE_BREAKPOINT - 1}px)`,
).matches;
if (isMobileScreen) {
showErrorOptions.position = 'top-center';
// showErrorOptions.transition = 'flip';
@@ -316,7 +321,7 @@ export const getTextContent = (message) => {
if (!message || !message.content) return '';
if (Array.isArray(message.content)) {
const textContent = message.content.find(item => item.type === 'text');
const textContent = message.content.find((item) => item.type === 'text');
return textContent?.text || '';
}
return typeof message.content === 'string' ? message.content : '';
@@ -341,15 +346,19 @@ export const processThinkTags = (content, reasoningContent = '') => {
}
replyParts.push(content.substring(lastIndex));
const processedContent = replyParts.join('').replace(/<\/?think>/g, '').trim();
const processedContent = replyParts
.join('')
.replace(/<\/?think>/g, '')
.trim();
const thoughtsStr = thoughts.join('\n\n---\n\n');
const processedReasoningContent = reasoningContent && thoughtsStr
? `${reasoningContent}\n\n---\n\n${thoughtsStr}`
: reasoningContent || thoughtsStr;
const processedReasoningContent =
reasoningContent && thoughtsStr
? `${reasoningContent}\n\n---\n\n${thoughtsStr}`
: reasoningContent || thoughtsStr;
return {
content: processedContent,
reasoningContent: processedReasoningContent
reasoningContent: processedReasoningContent,
};
};
@@ -364,10 +373,14 @@ export const processIncompleteThinkTags = (content, reasoningContent = '') => {
const fragmentAfterLastOpen = content.substring(lastOpenThinkIndex);
if (!fragmentAfterLastOpen.includes('</think>')) {
const unclosedThought = fragmentAfterLastOpen.substring('<think>'.length).trim();
const unclosedThought = fragmentAfterLastOpen
.substring('<think>'.length)
.trim();
const cleanContent = content.substring(0, lastOpenThinkIndex);
const processedReasoningContent = unclosedThought
? reasoningContent ? `${reasoningContent}\n\n---\n\n${unclosedThought}` : unclosedThought
? reasoningContent
? `${reasoningContent}\n\n---\n\n${unclosedThought}`
: unclosedThought
: reasoningContent;
return processThinkTags(cleanContent, processedReasoningContent);
@@ -377,20 +390,24 @@ export const processIncompleteThinkTags = (content, reasoningContent = '') => {
};
// 构建消息内容(包含图片)
export const buildMessageContent = (textContent, imageUrls = [], imageEnabled = false) => {
export const buildMessageContent = (
textContent,
imageUrls = [],
imageEnabled = false,
) => {
if (!textContent && (!imageUrls || imageUrls.length === 0)) {
return '';
}
const validImageUrls = imageUrls.filter(url => url && url.trim() !== '');
const validImageUrls = imageUrls.filter((url) => url && url.trim() !== '');
if (imageEnabled && validImageUrls.length > 0) {
return [
{ type: 'text', text: textContent || '' },
...validImageUrls.map(url => ({
...validImageUrls.map((url) => ({
type: 'image_url',
image_url: { url: url.trim() }
}))
image_url: { url: url.trim() },
})),
];
}
@@ -403,27 +420,26 @@ export const createMessage = (role, content, options = {}) => ({
content,
createAt: Date.now(),
id: generateMessageId(),
...options
...options,
});
// 创建加载中的助手消息
export const createLoadingAssistantMessage = () => createMessage(
MESSAGE_ROLES.ASSISTANT,
'',
{
export const createLoadingAssistantMessage = () =>
createMessage(MESSAGE_ROLES.ASSISTANT, '', {
reasoningContent: '',
isReasoningExpanded: true,
isThinkingComplete: false,
hasAutoCollapsed: false,
status: 'loading'
}
);
status: 'loading',
});
// 检查消息是否包含图片
export const hasImageContent = (message) => {
return message &&
return (
message &&
Array.isArray(message.content) &&
message.content.some(item => item.type === 'image_url');
message.content.some((item) => item.type === 'image_url')
);
};
// 格式化消息用于API请求
@@ -432,15 +448,13 @@ export const formatMessageForAPI = (message) => {
return {
role: message.role,
content: message.content
content: message.content,
};
};
// 验证消息是否有效
export const isValidMessage = (message) => {
return message &&
message.role &&
(message.content || message.content === '');
return message && message.role && (message.content || message.content === '');
};
// 获取最后一条用户消息
@@ -590,7 +604,10 @@ export const calculateModelPrice = ({
if (selectedGroup === 'all' || usedGroupRatio === undefined) {
// 在模型可用分组中选择倍率最小的分组,若无则使用 1
let minRatio = Number.POSITIVE_INFINITY;
if (Array.isArray(record.enable_groups) && record.enable_groups.length > 0) {
if (
Array.isArray(record.enable_groups) &&
record.enable_groups.length > 0
) {
record.enable_groups.forEach((g) => {
const r = groupRatio[g];
if (r !== undefined && r < minRatio) {
@@ -611,7 +628,8 @@ export const calculateModelPrice = ({
if (record.quota_type === 0) {
// 按量计费
const inputRatioPriceUSD = record.model_ratio * 2 * usedGroupRatio;
const completionRatioPriceUSD = record.model_ratio * record.completion_ratio * 2 * usedGroupRatio;
const completionRatioPriceUSD =
record.model_ratio * record.completion_ratio * 2 * usedGroupRatio;
const unitDivisor = tokenUnit === 'K' ? 1000 : 1;
const unitLabel = tokenUnit === 'K' ? 'K' : 'M';
@@ -619,8 +637,10 @@ export const calculateModelPrice = ({
const rawDisplayInput = displayPrice(inputRatioPriceUSD);
const rawDisplayCompletion = displayPrice(completionRatioPriceUSD);
const numInput = parseFloat(rawDisplayInput.replace(/[^0-9.]/g, '')) / unitDivisor;
const numCompletion = parseFloat(rawDisplayCompletion.replace(/[^0-9.]/g, '')) / unitDivisor;
const numInput =
parseFloat(rawDisplayInput.replace(/[^0-9.]/g, '')) / unitDivisor;
const numCompletion =
parseFloat(rawDisplayCompletion.replace(/[^0-9.]/g, '')) / unitDivisor;
return {
inputPrice: `${currency === 'CNY' ? '¥' : '$'}${numInput.toFixed(precision)}`,
@@ -703,7 +723,7 @@ export const createCardProPagination = ({
{/* 桌面端左侧总数信息 */}
{!isMobile && (
<span
className="text-sm select-none"
className='text-sm select-none'
style={{ color: 'var(--semi-color-text-2)' }}
>
{totalText}
@@ -719,7 +739,7 @@ export const createCardProPagination = ({
showSizeChanger={showSizeChanger}
onPageSizeChange={onPageSizeChange}
onPageChange={onPageChange}
size={isMobile ? "small" : "default"}
size={isMobile ? 'small' : 'default'}
showQuickJumper={isMobile}
showTotal
/>