/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import React from 'react'; import { Avatar, Space, Tag, Tooltip, Popover, Typography } from '@douyinfe/semi-ui'; import { timestamp2string, renderGroup, renderQuota, stringToColor, getLogOther, renderModelTag, renderClaudeLogContent, renderClaudeModelPriceSimple, renderLogContent, renderModelPriceSimple, renderAudioModelPrice, renderClaudeModelPrice, renderModelPrice } from '../../../helpers'; import { IconHelpCircle } from '@douyinfe/semi-icons'; import { Route } from 'lucide-react'; const colors = [ 'amber', 'blue', 'cyan', 'green', 'grey', 'indigo', 'light-blue', 'lime', 'orange', 'pink', 'purple', 'red', 'teal', 'violet', 'yellow', ]; // Render functions function renderType(type, t) { switch (type) { case 1: return ( {t('充值')} ); case 2: return ( {t('消费')} ); case 3: return ( {t('管理')} ); case 4: return ( {t('系统')} ); case 5: return ( {t('错误')} ); default: return ( {t('未知')} ); } } function renderIsStream(bool, t) { if (bool) { return ( {t('流')} ); } else { return ( {t('非流')} ); } } function renderUseTime(type, t) { const time = parseInt(type); if (time < 101) { return ( {' '} {time} s{' '} ); } else if (time < 300) { return ( {' '} {time} s{' '} ); } else { return ( {' '} {time} s{' '} ); } } function renderFirstUseTime(type, t) { let time = parseFloat(type) / 1000.0; time = time.toFixed(1); if (time < 3) { return ( {' '} {time} s{' '} ); } else if (time < 10) { return ( {' '} {time} s{' '} ); } else { return ( {' '} {time} s{' '} ); } } function renderModelName(record, copyText, t) { let other = getLogOther(record.other); let modelMapped = other?.is_model_mapped && other?.upstream_model_name && other?.upstream_model_name !== ''; if (!modelMapped) { return renderModelTag(record.model_name, { onClick: (event) => { copyText(event, record.model_name).then((r) => { }); }, }); } else { return ( <>
{t('请求并计费模型')}: {renderModelTag(record.model_name, { onClick: (event) => { copyText(event, record.model_name).then((r) => { }); }, })}
{t('实际模型')}: {renderModelTag(other.upstream_model_name, { onClick: (event) => { copyText(event, other.upstream_model_name).then( (r) => { }, ); }, })}
} > {renderModelTag(record.model_name, { onClick: (event) => { copyText(event, record.model_name).then((r) => { }); }, suffixIcon: ( ), })}
); } } export const getLogsColumns = ({ t, COLUMN_KEYS, copyText, showUserInfoFunc, isAdminUser, }) => { return [ { key: COLUMN_KEYS.TIME, title: t('时间'), dataIndex: 'timestamp2string', }, { key: COLUMN_KEYS.CHANNEL, title: t('渠道'), dataIndex: 'channel', render: (text, record, index) => { let isMultiKey = false; let multiKeyIndex = -1; let other = getLogOther(record.other); if (other?.admin_info) { let adminInfo = other.admin_info; if (adminInfo?.is_multi_key) { isMultiKey = true; multiKeyIndex = adminInfo.multi_key_index; } } return isAdminUser && (record.type === 0 || record.type === 2 || record.type === 5) ? ( {text} {isMultiKey && ( {multiKeyIndex} )} ) : null; }, }, { key: COLUMN_KEYS.USERNAME, title: t('用户'), dataIndex: 'username', render: (text, record, index) => { return isAdminUser ? (
{ event.stopPropagation(); showUserInfoFunc(record.user_id); }} > {typeof text === 'string' && text.slice(0, 1)} {text}
) : ( <> ); }, }, { key: COLUMN_KEYS.TOKEN, title: t('令牌'), dataIndex: 'token_name', render: (text, record, index) => { return record.type === 0 || record.type === 2 || record.type === 5 ? (
{ copyText(event, text); }} > {' '} {t(text)}{' '}
) : ( <> ); }, }, { key: COLUMN_KEYS.GROUP, title: t('分组'), dataIndex: 'group', render: (text, record, index) => { if (record.type === 0 || record.type === 2 || record.type === 5) { if (record.group) { return <>{renderGroup(record.group)}; } else { let other = null; try { other = JSON.parse(record.other); } catch (e) { console.error( `Failed to parse record.other: "${record.other}".`, e, ); } if (other === null) { return <>; } if (other.group !== undefined) { return <>{renderGroup(other.group)}; } else { return <>; } } } else { return <>; } }, }, { key: COLUMN_KEYS.TYPE, title: t('类型'), dataIndex: 'type', render: (text, record, index) => { return <>{renderType(text, t)}; }, }, { key: COLUMN_KEYS.MODEL, title: t('模型'), dataIndex: 'model_name', render: (text, record, index) => { return record.type === 0 || record.type === 2 || record.type === 5 ? ( <>{renderModelName(record, copyText, t)} ) : ( <> ); }, }, { key: COLUMN_KEYS.USE_TIME, title: t('用时/首字'), dataIndex: 'use_time', render: (text, record, index) => { if (!(record.type === 2 || record.type === 5)) { return <>; } if (record.is_stream) { let other = getLogOther(record.other); return ( <> {renderUseTime(text, t)} {renderFirstUseTime(other?.frt, t)} {renderIsStream(record.is_stream, t)} ); } else { return ( <> {renderUseTime(text, t)} {renderIsStream(record.is_stream, t)} ); } }, }, { key: COLUMN_KEYS.PROMPT, title: t('提示'), dataIndex: 'prompt_tokens', render: (text, record, index) => { return record.type === 0 || record.type === 2 || record.type === 5 ? ( <>{ {text} } ) : ( <> ); }, }, { key: COLUMN_KEYS.COMPLETION, title: t('补全'), dataIndex: 'completion_tokens', render: (text, record, index) => { return parseInt(text) > 0 && (record.type === 0 || record.type === 2 || record.type === 5) ? ( <>{ {text} } ) : ( <> ); }, }, { key: COLUMN_KEYS.COST, title: t('花费'), dataIndex: 'quota', render: (text, record, index) => { return record.type === 0 || record.type === 2 || record.type === 5 ? ( <>{renderQuota(text, 6)} ) : ( <> ); }, }, { key: COLUMN_KEYS.IP, title: (
{t('IP')}
), dataIndex: 'ip', render: (text, record, index) => { return (record.type === 2 || record.type === 5) && text ? ( { copyText(event, text); }} > {text} ) : ( <> ); }, }, { key: COLUMN_KEYS.RETRY, title: t('重试'), dataIndex: 'retry', render: (text, record, index) => { if (!(record.type === 2 || record.type === 5)) { return <>; } let content = t('渠道') + `:${record.channel}`; if (record.other !== '') { let other = JSON.parse(record.other); if (other === null) { return <>; } if (other.admin_info !== undefined) { if ( other.admin_info.use_channel !== null && other.admin_info.use_channel !== undefined && other.admin_info.use_channel !== '' ) { let useChannel = other.admin_info.use_channel; let useChannelStr = useChannel.join('->'); content = t('渠道') + `:${useChannelStr}`; } } } return isAdminUser ?
{content}
: <>; }, }, { key: COLUMN_KEYS.DETAILS, title: t('详情'), dataIndex: 'content', fixed: 'right', render: (text, record, index) => { let other = getLogOther(record.other); if (other == null || record.type !== 2) { return ( {text} ); } let content = other?.claude ? renderClaudeModelPriceSimple( other.model_ratio, other.model_price, other.group_ratio, other?.user_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?.user_group_ratio, other.cache_tokens || 0, other.cache_ratio || 1.0, ); return ( {content} ); }, }, ]; };