import React, { useEffect, useState } from 'react';
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 {
renderModelPrice,
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: '全部用户', value: 'all' },
{ key: 'self', text: '当前用户', value: 'self' },
];
const colors = [
'amber',
'blue',
'cyan',
'green',
'grey',
'indigo',
'light-blue',
'lime',
'orange',
'pink',
'purple',
'red',
'teal',
'violet',
'yellow',
];
function renderType(type) {
switch (type) {
case 1:
return (
{' '}
充值{' '}
);
case 2:
return (
{' '}
消费{' '}
);
case 3:
return (
{' '}
管理{' '}
);
case 4:
return (
{' '}
系统{' '}
);
default:
return (
{' '}
未知{' '}
);
}
}
function renderIsStream(bool) {
if (bool) {
return (
流
);
} else {
return (
非流
);
}
}
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 LogsTable = () => {
const columns = [
{
title: '时间',
dataIndex: 'timestamp2string',
},
{
title: '渠道',
dataIndex: 'channel',
className: isAdmin() ? 'tableShow' : 'tableHiddle',
render: (text, record, index) => {
return isAdminUser ? (
record.type === 0 || record.type === 2 ? (
{
{' '}
{text}{' '}
}
) : (
<>>
)
) : (
<>>
);
},
},
{
title: '用户',
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: '令牌',
dataIndex: 'token_name',
render: (text, record, index) => {
return record.type === 0 || record.type === 2 ? (
{
copyText(text);
}}
>
{' '}
{text}{' '}
) : (
<>>
);
},
},
{
title: '类型',
dataIndex: 'type',
render: (text, record, index) => {
return <>{renderType(text)}>;
},
},
{
title: '模型',
dataIndex: 'model_name',
render: (text, record, index) => {
return record.type === 0 || record.type === 2 ? (
<>
{
copyText(text);
}}
>
{' '}
{text}{' '}
>
) : (
<>>
);
},
},
{
title: '用时/首字',
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: '提示',
dataIndex: 'prompt_tokens',
render: (text, record, index) => {
return record.type === 0 || record.type === 2 ? (
<>{ {text} }>
) : (
<>>
);
},
},
{
title: '补全',
dataIndex: 'completion_tokens',
render: (text, record, index) => {
return parseInt(text) > 0 &&
(record.type === 0 || record.type === 2) ? (
<>{ {text} }>
) : (
<>>
);
},
},
{
title: '花费',
dataIndex: 'quota',
render: (text, record, index) => {
return record.type === 0 || record.type === 2 ? (
<>{renderQuota(text, 6)}>
) : (
<>>
);
},
},
// {
// title: '重试',
// dataIndex: 'retry',
// className: isAdmin() ? 'tableShow' : 'tableHiddle',
// render: (text, record, index) => {
// let content = '渠道:' + 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 = `渠道:${useChannelStr}`;
// }
// }
// }
// return isAdminUser ? {content}
: <>>;
// },
// },
{
title: '详情',
dataIndex: 'content',
render: (text, record, index) => {
let other = getLogOther(record.other);
if (other == null || record.type !== 2) {
return (
{text}
);
}
let content = renderModelPrice(
record.prompt_tokens,
record.completion_tokens,
other.model_ratio,
other.model_price,
other.completion_ratio,
other.group_ratio,
);
return (
{text}
);
},
},
];
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: '用户信息',
content: (
用户名: {data.username}
余额: {renderQuota(data.quota)}
已用额度:{renderQuota(data.used_quota)}
请求次数:{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 = '' + logs[i].id;
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) {
expandDataLocal.push({
key: '语音输入',
value: other.audio_input,
});
expandDataLocal.push({
key: '语音输出',
value: other.audio_output,
});
expandDataLocal.push({
key: '文字输入',
value: other.text_input,
});
expandDataLocal.push({
key: '文字输出',
value: other.text_output,
});
}
expandDataLocal.push({
key: '倍率详情',
value: logs[i].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 (
<>
总消耗额度: {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;