import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { API, copy, getTodayStartTimestamp, isAdmin, showError, showSuccess, timestamp2string, } from '../helpers'; import { Avatar, Button, Descriptions, Form, Layout, Modal, Select, Space, Spin, Table, Tag, Tooltip } from '@douyinfe/semi-ui'; import { ITEMS_PER_PAGE } from '../constants'; import { renderAudioModelPrice, renderModelPrice, renderModelPriceSimple, renderNumber, renderQuota, stringToColor } from '../helpers/render'; import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph'; import { getLogOther } from '../helpers/other.js'; const { Header } = Layout; function renderTimestamp(timestamp) { return <>{timestamp2string(timestamp)}; } const MODE_OPTIONS = [ { key: 'all', text: 'all', value: 'all' }, { key: 'self', text: 'current user', value: 'self' }, ]; const colors = [ 'amber', 'blue', 'cyan', 'green', 'grey', 'indigo', 'light-blue', 'lime', 'orange', 'pink', 'purple', 'red', 'teal', 'violet', 'yellow', ]; const LogsTable = () => { const { t } = useTranslation(); function renderType(type) { switch (type) { case 1: return {t('充值')}; case 2: return {t('消费')}; case 3: return {t('管理')}; case 4: return {t('系统')}; default: return {t('未知')}; } } function renderIsStream(bool) { if (bool) { return {t('流')}; } else { return {t('非流')}; } } function renderUseTime(type) { const time = parseInt(type); if (time < 101) { return ( {' '} {time} s{' '} ); } else if (time < 300) { return ( {' '} {time} s{' '} ); } else { return ( {' '} {time} s{' '} ); } } function renderFirstUseTime(type) { 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{' '} ); } } const columns = [ { title: t('时间'), dataIndex: 'timestamp2string', }, { title: t('渠道'), dataIndex: 'channel', className: isAdmin() ? 'tableShow' : 'tableHiddle', render: (text, record, index) => { return isAdminUser ? ( record.type === 0 || record.type === 2 ? (
{ {' '} {text}{' '} }
) : ( <> ) ) : ( <> ); }, }, { title: t('用户'), dataIndex: 'username', className: isAdmin() ? 'tableShow' : 'tableHiddle', render: (text, record, index) => { return isAdminUser ? (
showUserInfo(record.user_id)} > {typeof text === 'string' && text.slice(0, 1)} {text}
) : ( <> ); }, }, { title: t('令牌'), dataIndex: 'token_name', render: (text, record, index) => { return record.type === 0 || record.type === 2 ? (
{ copyText(text); }} > {' '} {t(text)}{' '}
) : ( <> ); }, }, { title: t('分组'), dataIndex: 'group', render: (text, record, index) => { if (record.type === 0 || record.type === 2) { let other = JSON.parse(record.other); if (other === null) { return <>; } if (other.group !== undefined) { return ( {' '} {other.group}{' '} ); } else { return <>; } } else { return <>; } }, }, { title: t('类型'), dataIndex: 'type', render: (text, record, index) => { return <>{renderType(text)}; }, }, { title: t('模型'), dataIndex: 'model_name', render: (text, record, index) => { return record.type === 0 || record.type === 2 ? ( <> { copyText(text); }} > {' '} {text}{' '} ) : ( <> ); }, }, { title: t('用时/首字'), dataIndex: 'use_time', render: (text, record, index) => { if (record.is_stream) { let other = getLogOther(record.other); return ( <> {renderUseTime(text)} {renderFirstUseTime(other.frt)} {renderIsStream(record.is_stream)} ); } else { return ( <> {renderUseTime(text)} {renderIsStream(record.is_stream)} ); } }, }, { title: t('提示'), dataIndex: 'prompt_tokens', render: (text, record, index) => { return record.type === 0 || record.type === 2 ? ( <>{ {text} } ) : ( <> ); }, }, { title: t('补全'), dataIndex: 'completion_tokens', render: (text, record, index) => { return parseInt(text) > 0 && (record.type === 0 || record.type === 2) ? ( <>{ {text} } ) : ( <> ); }, }, { title: t('花费'), dataIndex: 'quota', render: (text, record, index) => { return record.type === 0 || record.type === 2 ? ( <>{renderQuota(text, 6)} ) : ( <> ); }, }, { title: t('重试'), dataIndex: 'retry', className: isAdmin() ? 'tableShow' : 'tableHiddle', render: (text, record, index) => { 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 !== '' ) { // channel id array let useChannel = other.admin_info.use_channel; let useChannelStr = useChannel.join('->'); content = t('渠道') + `:${useChannelStr}`; } } } return isAdminUser ?
{content}
: <>; }, }, { title: t('详情'), dataIndex: 'content', render: (text, record, index) => { let other = getLogOther(record.other); if (other == null || record.type !== 2) { return ( {text} ); } let content = renderModelPriceSimple( other.model_ratio, other.model_price, other.group_ratio, ); return ( {content} ); }, }, ]; const [logs, setLogs] = useState([]); const [expandData, setExpandData] = useState({}); const [showStat, setShowStat] = useState(false); const [loading, setLoading] = useState(false); const [loadingStat, setLoadingStat] = useState(false); const [activePage, setActivePage] = useState(1); const [logCount, setLogCount] = useState(ITEMS_PER_PAGE); const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); const [logType, setLogType] = useState(0); const isAdminUser = isAdmin(); let now = new Date(); // 初始化start_timestamp为今天0点 const [inputs, setInputs] = useState({ username: '', token_name: '', model_name: '', start_timestamp: timestamp2string(getTodayStartTimestamp()), end_timestamp: timestamp2string(now.getTime() / 1000 + 3600), channel: '', }); const { username, token_name, model_name, start_timestamp, end_timestamp, channel, } = inputs; const [stat, setStat] = useState({ quota: 0, token: 0, }); const handleInputChange = (value, name) => { setInputs((inputs) => ({ ...inputs, [name]: value })); }; const getLogSelfStat = async () => { let localStartTimestamp = Date.parse(start_timestamp) / 1000; let localEndTimestamp = Date.parse(end_timestamp) / 1000; let url = `/api/log/self/stat?type=${logType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; url = encodeURI(url); let res = await API.get(url); const { success, message, data } = res.data; if (success) { setStat(data); } else { showError(message); } }; const getLogStat = async () => { let localStartTimestamp = Date.parse(start_timestamp) / 1000; let localEndTimestamp = Date.parse(end_timestamp) / 1000; let url = `/api/log/stat?type=${logType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}`; url = encodeURI(url); let res = await API.get(url); const { success, message, data } = res.data; if (success) { setStat(data); } else { showError(message); } }; const handleEyeClick = async () => { if (loadingStat) { return; } setLoadingStat(true); if (isAdminUser) { await getLogStat(); } else { await getLogSelfStat(); } setShowStat(true); setLoadingStat(false); }; const showUserInfo = async (userId) => { if (!isAdminUser) { return; } const res = await API.get(`/api/user/${userId}`); const { success, message, data } = res.data; if (success) { Modal.info({ title: t('用户信息'), content: (

{t('用户名')}: {data.username}

{t('余额')}: {renderQuota(data.quota)}

{t('已用额度')}:{renderQuota(data.used_quota)}

{t('请求次数')}:{renderNumber(data.request_count)}

), centered: true, }); } else { showError(message); } }; const setLogsFormat = (logs) => { let expandDatesLocal = {}; for (let i = 0; i < logs.length; i++) { logs[i].timestamp2string = timestamp2string(logs[i].created_at); logs[i].key = i; let other = getLogOther(logs[i].other); let expandDataLocal = []; if (isAdmin()) { // let content = '渠道:' + logs[i].channel; // if (other.admin_info !== undefined) { // if ( // other.admin_info.use_channel !== null && // other.admin_info.use_channel !== undefined && // other.admin_info.use_channel !== '' // ) { // // channel id array // let useChannel = other.admin_info.use_channel; // let useChannelStr = useChannel.join('->'); // content = `渠道:${useChannelStr}`; // } // } // expandDataLocal.push({ // key: '渠道重试', // value: content, // }) } if (other?.ws || other?.audio) { expandDataLocal.push({ key: t('语音输入'), value: other.audio_input, }); expandDataLocal.push({ key: t('语音输出'), value: other.audio_output, }); expandDataLocal.push({ key: t('文字输入'), value: other.text_input, }); expandDataLocal.push({ key: t('文字输出'), value: other.text_output, }); } expandDataLocal.push({ key: t('日志详情'), value: logs[i].content, }); if (logs[i].type === 2) { let content = ''; if (other?.ws || other?.audio) { content = renderAudioModelPrice( other.text_input, other.text_output, other.model_ratio, other.model_price, other.completion_ratio, other.audio_input, other.audio_output, other?.audio_ratio, other?.audio_completion_ratio, other.group_ratio, ); } else { content = renderModelPrice( logs[i].prompt_tokens, logs[i].completion_tokens, other.model_ratio, other.model_price, other.completion_ratio, other.group_ratio, ); } expandDataLocal.push({ key: t('计费过程'), value: content, }); } expandDatesLocal[logs[i].key] = expandDataLocal; } setExpandData(expandDatesLocal); setLogs(logs); }; const loadLogs = async (startIdx, pageSize, logType = 0) => { setLoading(true); let url = ''; let localStartTimestamp = Date.parse(start_timestamp) / 1000; let localEndTimestamp = Date.parse(end_timestamp) / 1000; if (isAdminUser) { url = `/api/log/?p=${startIdx}&page_size=${pageSize}&type=${logType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}`; } else { url = `/api/log/self/?p=${startIdx}&page_size=${pageSize}&type=${logType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; } url = encodeURI(url); const res = await API.get(url); const { success, message, data } = res.data; if (success) { const newPageData = data.items; setActivePage(data.page); setPageSize(data.page_size); setLogCount(data.total); setLogsFormat(newPageData); } else { showError(message); } setLoading(false); }; const handlePageChange = (page) => { setActivePage(page); loadLogs(page, pageSize, logType).then((r) => {}); }; const handlePageSizeChange = async (size) => { localStorage.setItem('page-size', size + ''); setPageSize(size); setActivePage(1); loadLogs(activePage, size) .then() .catch((reason) => { showError(reason); }); }; const refresh = async () => { setActivePage(1); handleEyeClick(); await loadLogs(activePage, pageSize, logType); }; const copyText = async (text) => { if (await copy(text)) { showSuccess('已复制:' + text); } else { Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text }); } }; useEffect(() => { const localPageSize = parseInt(localStorage.getItem('page-size')) || ITEMS_PER_PAGE; setPageSize(localPageSize); loadLogs(activePage, localPageSize) .then() .catch((reason) => { showError(reason); }); handleEyeClick(); }, []); const expandRowRender = (record, index) => { return ; }; return ( <>
{t('总消耗额度')}: {renderQuota(stat.quota)} RPM: {stat.rpm} TPM: {stat.tpm}
<> handleInputChange(value, 'token_name')} /> handleInputChange(value, 'model_name')} /> handleInputChange(value, 'start_timestamp')} /> handleInputChange(value, 'end_timestamp')} /> {isAdminUser && ( <> handleInputChange(value, 'channel')} /> handleInputChange(value, 'username')} /> )}
{ handlePageSizeChange(size); }, onPageChange: handlePageChange, }} /> ); }; export default LogsTable;