diff --git a/web/src/components/table/LogsTable.js b/web/src/components/table/LogsTable.js
deleted file mode 100644
index cea5d9bd..00000000
--- a/web/src/components/table/LogsTable.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// 重构后的 LogsTable - 使用新的模块化架构
-export { default } from './usage-logs/index.jsx';
\ No newline at end of file
diff --git a/web/src/components/table/MjLogsTable.js b/web/src/components/table/MjLogsTable.js
index 267a5be9..a5f614d0 100644
--- a/web/src/components/table/MjLogsTable.js
+++ b/web/src/components/table/MjLogsTable.js
@@ -1,970 +1,2 @@
-import React, { useEffect, useState } from 'react';
-import { useTranslation } from 'react-i18next';
-import {
- Palette,
- ZoomIn,
- Shuffle,
- Move,
- FileText,
- Blend,
- Upload,
- Minimize2,
- RotateCcw,
- PaintBucket,
- Focus,
- Move3D,
- Monitor,
- UserCheck,
- HelpCircle,
- CheckCircle,
- Clock,
- Copy,
- FileX,
- Pause,
- XCircle,
- Loader,
- AlertCircle,
- Hash,
-} from 'lucide-react';
-import {
- API,
- copy,
- isAdmin,
- showError,
- showSuccess,
- timestamp2string
-} from '../../helpers';
-
-import {
- Button,
- Checkbox,
- Empty,
- Form,
- ImagePreview,
- Layout,
- Modal,
- Progress,
- Skeleton,
- Table,
- Tag,
- Typography
-} from '@douyinfe/semi-ui';
-import CardPro from '../common/ui/CardPro';
-import {
- IllustrationNoResult,
- IllustrationNoResultDark
-} from '@douyinfe/semi-illustrations';
-import { ITEMS_PER_PAGE } from '../../constants';
-import {
- IconEyeOpened,
- IconSearch,
-} from '@douyinfe/semi-icons';
-import { useTableCompactMode } from '../../hooks/common/useTableCompactMode';
-
-const { Text } = Typography;
-
-const colors = [
- 'amber',
- 'blue',
- 'cyan',
- 'green',
- 'grey',
- 'indigo',
- 'light-blue',
- 'lime',
- 'orange',
- 'pink',
- 'purple',
- 'red',
- 'teal',
- 'violet',
- 'yellow',
-];
-
-// 定义列键值常量
-const COLUMN_KEYS = {
- SUBMIT_TIME: 'submit_time',
- DURATION: 'duration',
- CHANNEL: 'channel',
- TYPE: 'type',
- TASK_ID: 'task_id',
- SUBMIT_RESULT: 'submit_result',
- TASK_STATUS: 'task_status',
- PROGRESS: 'progress',
- IMAGE: 'image',
- PROMPT: 'prompt',
- PROMPT_EN: 'prompt_en',
- FAIL_REASON: 'fail_reason',
-};
-
-const LogsTable = () => {
- const { t } = useTranslation();
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [modalContent, setModalContent] = useState('');
-
- // 列可见性状态
- const [visibleColumns, setVisibleColumns] = useState({});
- const [showColumnSelector, setShowColumnSelector] = useState(false);
- const isAdminUser = isAdmin();
- const [compactMode, setCompactMode] = useTableCompactMode('mjLogs');
-
- // 加载保存的列偏好设置
- useEffect(() => {
- const savedColumns = localStorage.getItem('mj-logs-table-columns');
- if (savedColumns) {
- try {
- const parsed = JSON.parse(savedColumns);
- const defaults = getDefaultColumnVisibility();
- const merged = { ...defaults, ...parsed };
- setVisibleColumns(merged);
- } catch (e) {
- console.error('Failed to parse saved column preferences', e);
- initDefaultColumns();
- }
- } else {
- initDefaultColumns();
- }
- }, []);
-
- // 获取默认列可见性
- const getDefaultColumnVisibility = () => {
- return {
- [COLUMN_KEYS.SUBMIT_TIME]: true,
- [COLUMN_KEYS.DURATION]: true,
- [COLUMN_KEYS.CHANNEL]: isAdminUser,
- [COLUMN_KEYS.TYPE]: true,
- [COLUMN_KEYS.TASK_ID]: true,
- [COLUMN_KEYS.SUBMIT_RESULT]: isAdminUser,
- [COLUMN_KEYS.TASK_STATUS]: true,
- [COLUMN_KEYS.PROGRESS]: true,
- [COLUMN_KEYS.IMAGE]: true,
- [COLUMN_KEYS.PROMPT]: true,
- [COLUMN_KEYS.PROMPT_EN]: true,
- [COLUMN_KEYS.FAIL_REASON]: true,
- };
- };
-
- // 初始化默认列可见性
- const initDefaultColumns = () => {
- const defaults = getDefaultColumnVisibility();
- setVisibleColumns(defaults);
- localStorage.setItem('mj-logs-table-columns', JSON.stringify(defaults));
- };
-
- // 处理列可见性变化
- const handleColumnVisibilityChange = (columnKey, checked) => {
- const updatedColumns = { ...visibleColumns, [columnKey]: checked };
- setVisibleColumns(updatedColumns);
- };
-
- // 处理全选
- const handleSelectAll = (checked) => {
- const allKeys = Object.keys(COLUMN_KEYS).map((key) => COLUMN_KEYS[key]);
- const updatedColumns = {};
-
- allKeys.forEach((key) => {
- if ((key === COLUMN_KEYS.CHANNEL || key === COLUMN_KEYS.SUBMIT_RESULT) && !isAdminUser) {
- updatedColumns[key] = false;
- } else {
- updatedColumns[key] = checked;
- }
- });
-
- setVisibleColumns(updatedColumns);
- };
-
- // 更新表格时保存列可见性
- useEffect(() => {
- if (Object.keys(visibleColumns).length > 0) {
- localStorage.setItem('mj-logs-table-columns', JSON.stringify(visibleColumns));
- }
- }, [visibleColumns]);
-
- function renderType(type) {
- switch (type) {
- case 'IMAGINE':
- return (
- }>
- {t('绘图')}
-
- );
- case 'UPSCALE':
- return (
- }>
- {t('放大')}
-
- );
- case 'VIDEO':
- return (
- }>
- {t('视频')}
-
- );
- case 'EDITS':
- return (
- }>
- {t('编辑')}
-
- );
- case 'VARIATION':
- return (
- }>
- {t('变换')}
-
- );
- case 'HIGH_VARIATION':
- return (
- }>
- {t('强变换')}
-
- );
- case 'LOW_VARIATION':
- return (
- }>
- {t('弱变换')}
-
- );
- case 'PAN':
- return (
- }>
- {t('平移')}
-
- );
- case 'DESCRIBE':
- return (
- }>
- {t('图生文')}
-
- );
- case 'BLEND':
- return (
- }>
- {t('图混合')}
-
- );
- case 'UPLOAD':
- return (
- }>
- 上传文件
-
- );
- case 'SHORTEN':
- return (
- }>
- {t('缩词')}
-
- );
- case 'REROLL':
- return (
- }>
- {t('重绘')}
-
- );
- case 'INPAINT':
- return (
- }>
- {t('局部重绘-提交')}
-
- );
- case 'ZOOM':
- return (
- }>
- {t('变焦')}
-
- );
- case 'CUSTOM_ZOOM':
- return (
- }>
- {t('自定义变焦-提交')}
-
- );
- case 'MODAL':
- return (
- }>
- {t('窗口处理')}
-
- );
- case 'SWAP_FACE':
- return (
- }>
- {t('换脸')}
-
- );
- default:
- return (
- }>
- {t('未知')}
-
- );
- }
- }
-
- function renderCode(code) {
- switch (code) {
- case 1:
- return (
- }>
- {t('已提交')}
-
- );
- case 21:
- return (
- }>
- {t('等待中')}
-
- );
- case 22:
- return (
- }>
- {t('重复提交')}
-
- );
- case 0:
- return (
- }>
- {t('未提交')}
-
- );
- default:
- return (
- }>
- {t('未知')}
-
- );
- }
- }
-
- function renderStatus(type) {
- switch (type) {
- case 'SUCCESS':
- return (
- }>
- {t('成功')}
-
- );
- case 'NOT_START':
- return (
- }>
- {t('未启动')}
-
- );
- case 'SUBMITTED':
- return (
- }>
- {t('队列中')}
-
- );
- case 'IN_PROGRESS':
- return (
- }>
- {t('执行中')}
-
- );
- case 'FAILURE':
- return (
- }>
- {t('失败')}
-
- );
- case 'MODAL':
- return (
- }>
- {t('窗口等待')}
-
- );
- default:
- return (
- }>
- {t('未知')}
-
- );
- }
- }
-
- const renderTimestamp = (timestampInSeconds) => {
- const date = new Date(timestampInSeconds * 1000); // 从秒转换为毫秒
-
- const year = date.getFullYear(); // 获取年份
- const month = ('0' + (date.getMonth() + 1)).slice(-2); // 获取月份,从0开始需要+1,并保证两位数
- const day = ('0' + date.getDate()).slice(-2); // 获取日期,并保证两位数
- const hours = ('0' + date.getHours()).slice(-2); // 获取小时,并保证两位数
- const minutes = ('0' + date.getMinutes()).slice(-2); // 获取分钟,并保证两位数
- const seconds = ('0' + date.getSeconds()).slice(-2); // 获取秒钟,并保证两位数
-
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; // 格式化输出
- };
- // 修改renderDuration函数以包含颜色逻辑
- function renderDuration(submit_time, finishTime) {
- if (!submit_time || !finishTime) return 'N/A';
-
- const start = new Date(submit_time);
- const finish = new Date(finishTime);
- const durationMs = finish - start;
- const durationSec = (durationMs / 1000).toFixed(1);
- const color = durationSec > 60 ? 'red' : 'green';
-
- return (
- }>
- {durationSec} {t('秒')}
-
- );
- }
-
- // 定义所有列
- const allColumns = [
- {
- key: COLUMN_KEYS.SUBMIT_TIME,
- title: t('提交时间'),
- dataIndex: 'submit_time',
- render: (text, record, index) => {
- return
{renderTimestamp(text / 1000)}
;
- },
- },
- {
- key: COLUMN_KEYS.DURATION,
- title: t('花费时间'),
- dataIndex: 'finish_time',
- render: (finish, record) => {
- return renderDuration(record.submit_time, finish);
- },
- },
- {
- key: COLUMN_KEYS.CHANNEL,
- title: t('渠道'),
- dataIndex: 'channel_id',
- className: isAdmin() ? 'tableShow' : 'tableHiddle',
- render: (text, record, index) => {
- return isAdminUser ? (
-
- }
- onClick={() => {
- copyText(text);
- }}
- >
- {' '}
- {text}{' '}
-
-
- ) : (
- <>>
- );
- },
- },
- {
- key: COLUMN_KEYS.TYPE,
- title: t('类型'),
- dataIndex: 'action',
- render: (text, record, index) => {
- return {renderType(text)}
;
- },
- },
- {
- key: COLUMN_KEYS.TASK_ID,
- title: t('任务ID'),
- dataIndex: 'mj_id',
- render: (text, record, index) => {
- return {text}
;
- },
- },
- {
- key: COLUMN_KEYS.SUBMIT_RESULT,
- title: t('提交结果'),
- dataIndex: 'code',
- className: isAdmin() ? 'tableShow' : 'tableHiddle',
- render: (text, record, index) => {
- return isAdminUser ? {renderCode(text)}
: <>>;
- },
- },
- {
- key: COLUMN_KEYS.TASK_STATUS,
- title: t('任务状态'),
- dataIndex: 'status',
- className: isAdmin() ? 'tableShow' : 'tableHiddle',
- render: (text, record, index) => {
- return {renderStatus(text)}
;
- },
- },
- {
- key: COLUMN_KEYS.PROGRESS,
- title: t('进度'),
- dataIndex: 'progress',
- render: (text, record, index) => {
- return (
-
- );
- },
- },
- {
- key: COLUMN_KEYS.IMAGE,
- title: t('结果图片'),
- dataIndex: 'image_url',
- render: (text, record, index) => {
- if (!text) {
- return t('无');
- }
- return (
-
- );
- },
- },
- {
- key: COLUMN_KEYS.PROMPT,
- title: 'Prompt',
- dataIndex: 'prompt',
- render: (text, record, index) => {
- if (!text) {
- return t('无');
- }
-
- return (
- {
- setModalContent(text);
- setIsModalOpen(true);
- }}
- >
- {text}
-
- );
- },
- },
- {
- key: COLUMN_KEYS.PROMPT_EN,
- title: 'PromptEn',
- dataIndex: 'prompt_en',
- render: (text, record, index) => {
- if (!text) {
- return t('无');
- }
-
- return (
- {
- setModalContent(text);
- setIsModalOpen(true);
- }}
- >
- {text}
-
- );
- },
- },
- {
- key: COLUMN_KEYS.FAIL_REASON,
- title: t('失败原因'),
- dataIndex: 'fail_reason',
- fixed: 'right',
- render: (text, record, index) => {
- if (!text) {
- return t('无');
- }
-
- return (
- {
- setModalContent(text);
- setIsModalOpen(true);
- }}
- >
- {text}
-
- );
- },
- },
- ];
-
- // 根据可见性设置过滤列
- const getVisibleColumns = () => {
- return allColumns.filter((column) => visibleColumns[column.key]);
- };
-
- const [logs, setLogs] = useState([]);
- const [loading, setLoading] = useState(true);
- const [activePage, setActivePage] = useState(1);
- const [logCount, setLogCount] = useState(0);
- const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
- const [isModalOpenurl, setIsModalOpenurl] = useState(false);
- const [showBanner, setShowBanner] = useState(false);
-
- // 定义模态框图片URL的状态和更新函数
- const [modalImageUrl, setModalImageUrl] = useState('');
- let now = new Date();
-
- // Form 初始值
- const formInitValues = {
- channel_id: '',
- mj_id: '',
- dateRange: [
- timestamp2string(now.getTime() / 1000 - 2592000),
- timestamp2string(now.getTime() / 1000 + 3600)
- ],
- };
-
- // Form API 引用
- const [formApi, setFormApi] = useState(null);
-
- const [stat, setStat] = useState({
- quota: 0,
- token: 0,
- });
-
- // 获取表单值的辅助函数
- const getFormValues = () => {
- const formValues = formApi ? formApi.getValues() : {};
-
- // 处理时间范围
- let start_timestamp = timestamp2string(now.getTime() / 1000 - 2592000);
- let end_timestamp = timestamp2string(now.getTime() / 1000 + 3600);
-
- if (formValues.dateRange && Array.isArray(formValues.dateRange) && formValues.dateRange.length === 2) {
- start_timestamp = formValues.dateRange[0];
- end_timestamp = formValues.dateRange[1];
- }
-
- return {
- channel_id: formValues.channel_id || '',
- mj_id: formValues.mj_id || '',
- start_timestamp,
- end_timestamp,
- };
- };
-
- const enrichLogs = (items) => {
- return items.map((log) => ({
- ...log,
- timestamp2string: timestamp2string(log.created_at),
- key: '' + log.id,
- }));
- };
-
- const syncPageData = (payload) => {
- const items = enrichLogs(payload.items || []);
- setLogs(items);
- setLogCount(payload.total || 0);
- setActivePage(payload.page || 1);
- setPageSize(payload.page_size || pageSize);
- };
-
- const loadLogs = async (page = 1, size = pageSize) => {
- setLoading(true);
- const { channel_id, mj_id, start_timestamp, end_timestamp } = getFormValues();
- let localStartTimestamp = Date.parse(start_timestamp);
- let localEndTimestamp = Date.parse(end_timestamp);
- const url = isAdminUser
- ? `/api/mj/?p=${page}&page_size=${size}&channel_id=${channel_id}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`
- : `/api/mj/self/?p=${page}&page_size=${size}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
- const res = await API.get(url);
- const { success, message, data } = res.data;
- if (success) {
- syncPageData(data);
- } else {
- showError(message);
- }
- setLoading(false);
- };
-
- const pageData = logs;
-
- const handlePageChange = (page) => {
- loadLogs(page, pageSize).then();
- };
-
- const handlePageSizeChange = async (size) => {
- localStorage.setItem('mj-page-size', size + '');
- await loadLogs(1, size);
- };
-
- const refresh = async () => {
- await loadLogs(1, pageSize);
- };
-
- const copyText = async (text) => {
- if (await copy(text)) {
- showSuccess(t('已复制:') + text);
- } else {
- // setSearchKeyword(text);
- Modal.error({ title: t('无法复制到剪贴板,请手动复制'), content: text });
- }
- };
-
- useEffect(() => {
- const localPageSize = parseInt(localStorage.getItem('mj-page-size')) || ITEMS_PER_PAGE;
- setPageSize(localPageSize);
- loadLogs(1, localPageSize).then();
- }, []);
-
- useEffect(() => {
- const mjNotifyEnabled = localStorage.getItem('mj_notify_enabled');
- if (mjNotifyEnabled !== 'true') {
- setShowBanner(true);
- }
- }, []);
-
- // 列选择器模态框
- const renderColumnSelector = () => {
- return (
- setShowColumnSelector(false)}
- footer={
-
-
-
-
-
- }
- >
-
- v === true)}
- indeterminate={
- Object.values(visibleColumns).some((v) => v === true) &&
- !Object.values(visibleColumns).every((v) => v === true)
- }
- onChange={(e) => handleSelectAll(e.target.checked)}
- >
- {t('全选')}
-
-
-
- {allColumns.map((column) => {
- // 为非管理员用户跳过管理员专用列
- if (
- !isAdminUser &&
- (column.key === COLUMN_KEYS.CHANNEL ||
- column.key === COLUMN_KEYS.SUBMIT_RESULT)
- ) {
- return null;
- }
-
- return (
-
-
- handleColumnVisibilityChange(column.key, e.target.checked)
- }
- >
- {column.title}
-
-
- );
- })}
-
-
- );
- };
-
- return (
- <>
- {renderColumnSelector()}
-
-
-
-
- {loading ? (
-
- ) : (
-
- {isAdminUser && showBanner
- ? t('当前未开启Midjourney回调,部分项目可能无法获得绘图结果,可在运营设置中开启。')
- : t('Midjourney 任务记录')}
-
- )}
-
-
-
- }
- searchArea={
-
- }
- >
- rest) : getVisibleColumns()}
- dataSource={logs}
- rowKey='key'
- loading={loading}
- scroll={compactMode ? undefined : { x: 'max-content' }}
- className="rounded-xl overflow-hidden"
- size="middle"
- empty={
- }
- darkModeImage={}
- description={t('搜索无结果')}
- style={{ padding: 30 }}
- />
- }
- pagination={{
- currentPage: activePage,
- pageSize: pageSize,
- total: logCount,
- pageSizeOptions: [10, 20, 50, 100],
- showSizeChanger: true,
- onPageSizeChange: handlePageSizeChange,
- onPageChange: handlePageChange,
- }}
- />
-
-
- setIsModalOpen(false)}
- onCancel={() => setIsModalOpen(false)}
- closable={null}
- bodyStyle={{ height: '400px', overflow: 'auto' }} // 设置模态框内容区域样式
- width={800} // 设置模态框宽度
- >
- {modalContent}
-
- setIsModalOpenurl(visible)}
- />
-
- >
- );
-};
-
-export default LogsTable;
+// 重构后的 MjLogsTable - 使用新的模块化架构
+export { default } from './mj-logs/index.jsx';
\ No newline at end of file
diff --git a/web/src/components/table/UsageLogsTable.js b/web/src/components/table/UsageLogsTable.js
new file mode 100644
index 00000000..da0623ae
--- /dev/null
+++ b/web/src/components/table/UsageLogsTable.js
@@ -0,0 +1,2 @@
+// 重构后的 UsageLogsTable - 使用新的模块化架构
+export { default } from './usage-logs/index.jsx';
\ No newline at end of file
diff --git a/web/src/components/table/mj-logs/MjLogsActions.jsx b/web/src/components/table/mj-logs/MjLogsActions.jsx
new file mode 100644
index 00000000..85815c33
--- /dev/null
+++ b/web/src/components/table/mj-logs/MjLogsActions.jsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import { Button, Skeleton, Typography } from '@douyinfe/semi-ui';
+import { IconEyeOpened } from '@douyinfe/semi-icons';
+
+const { Text } = Typography;
+
+const MjLogsActions = ({
+ loading,
+ showBanner,
+ isAdminUser,
+ compactMode,
+ setCompactMode,
+ t,
+}) => {
+ return (
+
+
+
+ {loading ? (
+
+ ) : (
+
+ {isAdminUser && showBanner
+ ? t('当前未开启Midjourney回调,部分项目可能无法获得绘图结果,可在运营设置中开启。')
+ : t('Midjourney 任务记录')}
+
+ )}
+
+
+
+ );
+};
+
+export default MjLogsActions;
\ No newline at end of file
diff --git a/web/src/components/table/mj-logs/MjLogsColumnDefs.js b/web/src/components/table/mj-logs/MjLogsColumnDefs.js
new file mode 100644
index 00000000..9e993785
--- /dev/null
+++ b/web/src/components/table/mj-logs/MjLogsColumnDefs.js
@@ -0,0 +1,477 @@
+import React from 'react';
+import {
+ Button,
+ Progress,
+ Tag,
+ Typography
+} from '@douyinfe/semi-ui';
+import {
+ Palette,
+ ZoomIn,
+ Shuffle,
+ Move,
+ FileText,
+ Blend,
+ Upload,
+ Minimize2,
+ RotateCcw,
+ PaintBucket,
+ Focus,
+ Move3D,
+ Monitor,
+ UserCheck,
+ HelpCircle,
+ CheckCircle,
+ Clock,
+ Copy,
+ FileX,
+ Pause,
+ XCircle,
+ Loader,
+ AlertCircle,
+ Hash,
+ Video
+} 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 'IMAGINE':
+ return (
+ }>
+ {t('绘图')}
+
+ );
+ case 'UPSCALE':
+ return (
+ }>
+ {t('放大')}
+
+ );
+ case 'VIDEO':
+ return (
+ }>
+ {t('视频')}
+
+ );
+ case 'EDITS':
+ return (
+ }>
+ {t('编辑')}
+
+ );
+ case 'VARIATION':
+ return (
+ }>
+ {t('变换')}
+
+ );
+ case 'HIGH_VARIATION':
+ return (
+ }>
+ {t('强变换')}
+
+ );
+ case 'LOW_VARIATION':
+ return (
+ }>
+ {t('弱变换')}
+
+ );
+ case 'PAN':
+ return (
+ }>
+ {t('平移')}
+
+ );
+ case 'DESCRIBE':
+ return (
+ }>
+ {t('图生文')}
+
+ );
+ case 'BLEND':
+ return (
+ }>
+ {t('图混合')}
+
+ );
+ case 'UPLOAD':
+ return (
+ }>
+ 上传文件
+
+ );
+ case 'SHORTEN':
+ return (
+ }>
+ {t('缩词')}
+
+ );
+ case 'REROLL':
+ return (
+ }>
+ {t('重绘')}
+
+ );
+ case 'INPAINT':
+ return (
+ }>
+ {t('局部重绘-提交')}
+
+ );
+ case 'ZOOM':
+ return (
+ }>
+ {t('变焦')}
+
+ );
+ case 'CUSTOM_ZOOM':
+ return (
+ }>
+ {t('自定义变焦-提交')}
+
+ );
+ case 'MODAL':
+ return (
+ }>
+ {t('窗口处理')}
+
+ );
+ case 'SWAP_FACE':
+ return (
+ }>
+ {t('换脸')}
+
+ );
+ default:
+ return (
+ }>
+ {t('未知')}
+
+ );
+ }
+}
+
+function renderCode(code, t) {
+ switch (code) {
+ case 1:
+ return (
+ }>
+ {t('已提交')}
+
+ );
+ case 21:
+ return (
+ }>
+ {t('等待中')}
+
+ );
+ case 22:
+ return (
+ }>
+ {t('重复提交')}
+
+ );
+ case 0:
+ return (
+ }>
+ {t('未提交')}
+
+ );
+ default:
+ return (
+ }>
+ {t('未知')}
+
+ );
+ }
+}
+
+function renderStatus(type, t) {
+ switch (type) {
+ case 'SUCCESS':
+ return (
+ }>
+ {t('成功')}
+
+ );
+ case 'NOT_START':
+ return (
+ }>
+ {t('未启动')}
+
+ );
+ case 'SUBMITTED':
+ return (
+ }>
+ {t('队列中')}
+
+ );
+ case 'IN_PROGRESS':
+ return (
+ }>
+ {t('执行中')}
+
+ );
+ case 'FAILURE':
+ return (
+ }>
+ {t('失败')}
+
+ );
+ case 'MODAL':
+ return (
+ }>
+ {t('窗口等待')}
+
+ );
+ default:
+ return (
+ }>
+ {t('未知')}
+
+ );
+ }
+}
+
+const renderTimestamp = (timestampInSeconds) => {
+ const date = new Date(timestampInSeconds * 1000);
+ const year = date.getFullYear();
+ const month = ('0' + (date.getMonth() + 1)).slice(-2);
+ const day = ('0' + date.getDate()).slice(-2);
+ const hours = ('0' + date.getHours()).slice(-2);
+ const minutes = ('0' + date.getMinutes()).slice(-2);
+ const seconds = ('0' + date.getSeconds()).slice(-2);
+
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+};
+
+function renderDuration(submit_time, finishTime, t) {
+ if (!submit_time || !finishTime) return 'N/A';
+
+ const start = new Date(submit_time);
+ const finish = new Date(finishTime);
+ const durationMs = finish - start;
+ const durationSec = (durationMs / 1000).toFixed(1);
+ const color = durationSec > 60 ? 'red' : 'green';
+
+ return (
+ }>
+ {durationSec} {t('秒')}
+
+ );
+}
+
+export const getMjLogsColumns = ({
+ t,
+ COLUMN_KEYS,
+ copyText,
+ openContentModal,
+ openImageModal,
+ isAdminUser,
+}) => {
+ return [
+ {
+ key: COLUMN_KEYS.SUBMIT_TIME,
+ title: t('提交时间'),
+ dataIndex: 'submit_time',
+ render: (text, record, index) => {
+ return {renderTimestamp(text / 1000)}
;
+ },
+ },
+ {
+ key: COLUMN_KEYS.DURATION,
+ title: t('花费时间'),
+ dataIndex: 'finish_time',
+ render: (finish, record) => {
+ return renderDuration(record.submit_time, finish, t);
+ },
+ },
+ {
+ key: COLUMN_KEYS.CHANNEL,
+ title: t('渠道'),
+ dataIndex: 'channel_id',
+ render: (text, record, index) => {
+ return isAdminUser ? (
+
+ }
+ onClick={() => {
+ copyText(text);
+ }}
+ >
+ {' '}
+ {text}{' '}
+
+
+ ) : (
+ <>>
+ );
+ },
+ },
+ {
+ key: COLUMN_KEYS.TYPE,
+ title: t('类型'),
+ dataIndex: 'action',
+ render: (text, record, index) => {
+ return {renderType(text, t)}
;
+ },
+ },
+ {
+ key: COLUMN_KEYS.TASK_ID,
+ title: t('任务ID'),
+ dataIndex: 'mj_id',
+ render: (text, record, index) => {
+ return {text}
;
+ },
+ },
+ {
+ key: COLUMN_KEYS.SUBMIT_RESULT,
+ title: t('提交结果'),
+ dataIndex: 'code',
+ render: (text, record, index) => {
+ return isAdminUser ? {renderCode(text, t)}
: <>>;
+ },
+ },
+ {
+ key: COLUMN_KEYS.TASK_STATUS,
+ title: t('任务状态'),
+ dataIndex: 'status',
+ render: (text, record, index) => {
+ return {renderStatus(text, t)}
;
+ },
+ },
+ {
+ key: COLUMN_KEYS.PROGRESS,
+ title: t('进度'),
+ dataIndex: 'progress',
+ render: (text, record, index) => {
+ return (
+
+ );
+ },
+ },
+ {
+ key: COLUMN_KEYS.IMAGE,
+ title: t('结果图片'),
+ dataIndex: 'image_url',
+ render: (text, record, index) => {
+ if (!text) {
+ return t('无');
+ }
+ return (
+
+ );
+ },
+ },
+ {
+ key: COLUMN_KEYS.PROMPT,
+ title: 'Prompt',
+ dataIndex: 'prompt',
+ render: (text, record, index) => {
+ if (!text) {
+ return t('无');
+ }
+
+ return (
+ {
+ openContentModal(text);
+ }}
+ >
+ {text}
+
+ );
+ },
+ },
+ {
+ key: COLUMN_KEYS.PROMPT_EN,
+ title: 'PromptEn',
+ dataIndex: 'prompt_en',
+ render: (text, record, index) => {
+ if (!text) {
+ return t('无');
+ }
+
+ return (
+ {
+ openContentModal(text);
+ }}
+ >
+ {text}
+
+ );
+ },
+ },
+ {
+ key: COLUMN_KEYS.FAIL_REASON,
+ title: t('失败原因'),
+ dataIndex: 'fail_reason',
+ fixed: 'right',
+ render: (text, record, index) => {
+ if (!text) {
+ return t('无');
+ }
+
+ return (
+ {
+ openContentModal(text);
+ }}
+ >
+ {text}
+
+ );
+ },
+ },
+ ];
+};
\ No newline at end of file
diff --git a/web/src/components/table/mj-logs/MjLogsFilters.jsx b/web/src/components/table/mj-logs/MjLogsFilters.jsx
new file mode 100644
index 00000000..3cfa6d3b
--- /dev/null
+++ b/web/src/components/table/mj-logs/MjLogsFilters.jsx
@@ -0,0 +1,104 @@
+import React from 'react';
+import { Button, Form } from '@douyinfe/semi-ui';
+import { IconSearch } from '@douyinfe/semi-icons';
+
+const MjLogsFilters = ({
+ formInitValues,
+ setFormApi,
+ refresh,
+ setShowColumnSelector,
+ formApi,
+ loading,
+ isAdminUser,
+ t,
+}) => {
+ return (
+
+ );
+};
+
+export default MjLogsFilters;
\ No newline at end of file
diff --git a/web/src/components/table/mj-logs/MjLogsTable.jsx b/web/src/components/table/mj-logs/MjLogsTable.jsx
new file mode 100644
index 00000000..f440c8df
--- /dev/null
+++ b/web/src/components/table/mj-logs/MjLogsTable.jsx
@@ -0,0 +1,96 @@
+import React, { useMemo } from 'react';
+import { Table, Empty } from '@douyinfe/semi-ui';
+import {
+ IllustrationNoResult,
+ IllustrationNoResultDark,
+} from '@douyinfe/semi-illustrations';
+import { getMjLogsColumns } from './MjLogsColumnDefs.js';
+
+const MjLogsTable = (mjLogsData) => {
+ const {
+ logs,
+ loading,
+ activePage,
+ pageSize,
+ logCount,
+ compactMode,
+ visibleColumns,
+ handlePageChange,
+ handlePageSizeChange,
+ copyText,
+ openContentModal,
+ openImageModal,
+ isAdminUser,
+ t,
+ COLUMN_KEYS,
+ } = mjLogsData;
+
+ // Get all columns
+ const allColumns = useMemo(() => {
+ return getMjLogsColumns({
+ t,
+ COLUMN_KEYS,
+ copyText,
+ openContentModal,
+ openImageModal,
+ isAdminUser,
+ });
+ }, [
+ t,
+ COLUMN_KEYS,
+ copyText,
+ openContentModal,
+ openImageModal,
+ isAdminUser,
+ ]);
+
+ // Filter columns based on visibility settings
+ const getVisibleColumns = () => {
+ return allColumns.filter((column) => visibleColumns[column.key]);
+ };
+
+ const visibleColumnsList = useMemo(() => {
+ return getVisibleColumns();
+ }, [visibleColumns, allColumns]);
+
+ const tableColumns = useMemo(() => {
+ return compactMode
+ ? visibleColumnsList.map(({ fixed, ...rest }) => rest)
+ : visibleColumnsList;
+ }, [compactMode, visibleColumnsList]);
+
+ return (
+
+ }
+ darkModeImage={
+
+ }
+ description={t('搜索无结果')}
+ style={{ padding: 30 }}
+ />
+ }
+ pagination={{
+ currentPage: activePage,
+ pageSize: pageSize,
+ total: logCount,
+ pageSizeOptions: [10, 20, 50, 100],
+ showSizeChanger: true,
+ onPageSizeChange: handlePageSizeChange,
+ onPageChange: handlePageChange,
+ }}
+ />
+ );
+};
+
+export default MjLogsTable;
\ No newline at end of file
diff --git a/web/src/components/table/mj-logs/index.jsx b/web/src/components/table/mj-logs/index.jsx
new file mode 100644
index 00000000..a017d390
--- /dev/null
+++ b/web/src/components/table/mj-logs/index.jsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { Layout } from '@douyinfe/semi-ui';
+import CardPro from '../../common/ui/CardPro.js';
+import MjLogsTable from './MjLogsTable.jsx';
+import MjLogsActions from './MjLogsActions.jsx';
+import MjLogsFilters from './MjLogsFilters.jsx';
+import ColumnSelectorModal from './modals/ColumnSelectorModal.jsx';
+import ContentModal from './modals/ContentModal.jsx';
+import { useMjLogsData } from '../../../hooks/mj-logs/useMjLogsData.js';
+
+const MjLogsPage = () => {
+ const mjLogsData = useMjLogsData();
+
+ return (
+ <>
+ {/* Modals */}
+
+
+
+
+ }
+ searchArea={}
+ >
+
+
+
+ >
+ );
+};
+
+export default MjLogsPage;
\ No newline at end of file
diff --git a/web/src/components/table/mj-logs/modals/ColumnSelectorModal.jsx b/web/src/components/table/mj-logs/modals/ColumnSelectorModal.jsx
new file mode 100644
index 00000000..3a9f0070
--- /dev/null
+++ b/web/src/components/table/mj-logs/modals/ColumnSelectorModal.jsx
@@ -0,0 +1,92 @@
+import React from 'react';
+import { Modal, Button, Checkbox } from '@douyinfe/semi-ui';
+import { getMjLogsColumns } from '../MjLogsColumnDefs.js';
+
+const ColumnSelectorModal = ({
+ showColumnSelector,
+ setShowColumnSelector,
+ visibleColumns,
+ handleColumnVisibilityChange,
+ handleSelectAll,
+ initDefaultColumns,
+ COLUMN_KEYS,
+ isAdminUser,
+ copyText,
+ openContentModal,
+ openImageModal,
+ t,
+}) => {
+ // Get all columns for display in selector
+ const allColumns = getMjLogsColumns({
+ t,
+ COLUMN_KEYS,
+ copyText,
+ openContentModal,
+ openImageModal,
+ isAdminUser,
+ });
+
+ return (
+ setShowColumnSelector(false)}
+ footer={
+
+
+
+
+
+ }
+ >
+
+ v === true)}
+ indeterminate={
+ Object.values(visibleColumns).some((v) => v === true) &&
+ !Object.values(visibleColumns).every((v) => v === true)
+ }
+ onChange={(e) => handleSelectAll(e.target.checked)}
+ >
+ {t('全选')}
+
+
+
+ {allColumns.map((column) => {
+ // Skip admin-only columns for non-admin users
+ if (
+ !isAdminUser &&
+ (column.key === COLUMN_KEYS.CHANNEL ||
+ column.key === COLUMN_KEYS.SUBMIT_RESULT)
+ ) {
+ return null;
+ }
+
+ return (
+
+
+ handleColumnVisibilityChange(column.key, e.target.checked)
+ }
+ >
+ {column.title}
+
+
+ );
+ })}
+
+
+ );
+};
+
+export default ColumnSelectorModal;
\ No newline at end of file
diff --git a/web/src/components/table/mj-logs/modals/ContentModal.jsx b/web/src/components/table/mj-logs/modals/ContentModal.jsx
new file mode 100644
index 00000000..0dd63bec
--- /dev/null
+++ b/web/src/components/table/mj-logs/modals/ContentModal.jsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import { Modal, ImagePreview } from '@douyinfe/semi-ui';
+
+const ContentModal = ({
+ isModalOpen,
+ setIsModalOpen,
+ modalContent,
+ isModalOpenurl,
+ setIsModalOpenurl,
+ modalImageUrl,
+}) => {
+ return (
+ <>
+ {/* Text Content Modal */}
+ setIsModalOpen(false)}
+ onCancel={() => setIsModalOpen(false)}
+ closable={null}
+ bodyStyle={{ height: '400px', overflow: 'auto' }}
+ width={800}
+ >
+ {modalContent}
+
+
+ {/* Image Preview Modal */}
+ setIsModalOpenurl(visible)}
+ />
+ >
+ );
+};
+
+export default ContentModal;
\ No newline at end of file
diff --git a/web/src/hooks/mj-logs/useMjLogsData.js b/web/src/hooks/mj-logs/useMjLogsData.js
new file mode 100644
index 00000000..906cd6fc
--- /dev/null
+++ b/web/src/hooks/mj-logs/useMjLogsData.js
@@ -0,0 +1,307 @@
+import { useState, useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Modal } from '@douyinfe/semi-ui';
+import {
+ API,
+ copy,
+ isAdmin,
+ showError,
+ showSuccess,
+ timestamp2string
+} from '../../helpers';
+import { ITEMS_PER_PAGE } from '../../constants';
+import { useTableCompactMode } from '../common/useTableCompactMode';
+
+export const useMjLogsData = () => {
+ const { t } = useTranslation();
+
+ // Define column keys for selection
+ const COLUMN_KEYS = {
+ SUBMIT_TIME: 'submit_time',
+ DURATION: 'duration',
+ CHANNEL: 'channel',
+ TYPE: 'type',
+ TASK_ID: 'task_id',
+ SUBMIT_RESULT: 'submit_result',
+ TASK_STATUS: 'task_status',
+ PROGRESS: 'progress',
+ IMAGE: 'image',
+ PROMPT: 'prompt',
+ PROMPT_EN: 'prompt_en',
+ FAIL_REASON: 'fail_reason',
+ };
+
+ // Basic state
+ const [logs, setLogs] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [activePage, setActivePage] = useState(1);
+ const [logCount, setLogCount] = useState(0);
+ const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
+ const [showBanner, setShowBanner] = useState(false);
+
+ // User and admin
+ const isAdminUser = isAdmin();
+
+ // Modal states
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [modalContent, setModalContent] = useState('');
+ const [isModalOpenurl, setIsModalOpenurl] = useState(false);
+ const [modalImageUrl, setModalImageUrl] = useState('');
+
+ // Form state
+ const [formApi, setFormApi] = useState(null);
+ let now = new Date();
+ const formInitValues = {
+ channel_id: '',
+ mj_id: '',
+ dateRange: [
+ timestamp2string(now.getTime() / 1000 - 2592000),
+ timestamp2string(now.getTime() / 1000 + 3600)
+ ],
+ };
+
+ // Column visibility state
+ const [visibleColumns, setVisibleColumns] = useState({});
+ const [showColumnSelector, setShowColumnSelector] = useState(false);
+
+ // Compact mode
+ const [compactMode, setCompactMode] = useTableCompactMode('mjLogs');
+
+ // Load saved column preferences from localStorage
+ useEffect(() => {
+ const savedColumns = localStorage.getItem('mj-logs-table-columns');
+ if (savedColumns) {
+ try {
+ const parsed = JSON.parse(savedColumns);
+ const defaults = getDefaultColumnVisibility();
+ const merged = { ...defaults, ...parsed };
+ setVisibleColumns(merged);
+ } catch (e) {
+ console.error('Failed to parse saved column preferences', e);
+ initDefaultColumns();
+ }
+ } else {
+ initDefaultColumns();
+ }
+ }, []);
+
+ // Check banner notification
+ useEffect(() => {
+ const mjNotifyEnabled = localStorage.getItem('mj_notify_enabled');
+ if (mjNotifyEnabled !== 'true') {
+ setShowBanner(true);
+ }
+ }, []);
+
+ // Get default column visibility based on user role
+ const getDefaultColumnVisibility = () => {
+ return {
+ [COLUMN_KEYS.SUBMIT_TIME]: true,
+ [COLUMN_KEYS.DURATION]: true,
+ [COLUMN_KEYS.CHANNEL]: isAdminUser,
+ [COLUMN_KEYS.TYPE]: true,
+ [COLUMN_KEYS.TASK_ID]: true,
+ [COLUMN_KEYS.SUBMIT_RESULT]: isAdminUser,
+ [COLUMN_KEYS.TASK_STATUS]: true,
+ [COLUMN_KEYS.PROGRESS]: true,
+ [COLUMN_KEYS.IMAGE]: true,
+ [COLUMN_KEYS.PROMPT]: true,
+ [COLUMN_KEYS.PROMPT_EN]: true,
+ [COLUMN_KEYS.FAIL_REASON]: true,
+ };
+ };
+
+ // Initialize default column visibility
+ const initDefaultColumns = () => {
+ const defaults = getDefaultColumnVisibility();
+ setVisibleColumns(defaults);
+ localStorage.setItem('mj-logs-table-columns', JSON.stringify(defaults));
+ };
+
+ // Handle column visibility change
+ const handleColumnVisibilityChange = (columnKey, checked) => {
+ const updatedColumns = { ...visibleColumns, [columnKey]: checked };
+ setVisibleColumns(updatedColumns);
+ };
+
+ // Handle "Select All" checkbox
+ const handleSelectAll = (checked) => {
+ const allKeys = Object.keys(COLUMN_KEYS).map((key) => COLUMN_KEYS[key]);
+ const updatedColumns = {};
+
+ allKeys.forEach((key) => {
+ if (
+ (key === COLUMN_KEYS.CHANNEL || key === COLUMN_KEYS.SUBMIT_RESULT) &&
+ !isAdminUser
+ ) {
+ updatedColumns[key] = false;
+ } else {
+ updatedColumns[key] = checked;
+ }
+ });
+
+ setVisibleColumns(updatedColumns);
+ };
+
+ // Update table when column visibility changes
+ useEffect(() => {
+ if (Object.keys(visibleColumns).length > 0) {
+ localStorage.setItem('mj-logs-table-columns', JSON.stringify(visibleColumns));
+ }
+ }, [visibleColumns]);
+
+ // Get form values helper function
+ const getFormValues = () => {
+ const formValues = formApi ? formApi.getValues() : {};
+
+ let start_timestamp = timestamp2string(now.getTime() / 1000 - 2592000);
+ let end_timestamp = timestamp2string(now.getTime() / 1000 + 3600);
+
+ if (
+ formValues.dateRange &&
+ Array.isArray(formValues.dateRange) &&
+ formValues.dateRange.length === 2
+ ) {
+ start_timestamp = formValues.dateRange[0];
+ end_timestamp = formValues.dateRange[1];
+ }
+
+ return {
+ channel_id: formValues.channel_id || '',
+ mj_id: formValues.mj_id || '',
+ start_timestamp,
+ end_timestamp,
+ };
+ };
+
+ // Enrich logs data
+ const enrichLogs = (items) => {
+ return items.map((log) => ({
+ ...log,
+ timestamp2string: timestamp2string(log.created_at),
+ key: '' + log.id,
+ }));
+ };
+
+ // Sync page data
+ const syncPageData = (payload) => {
+ const items = enrichLogs(payload.items || []);
+ setLogs(items);
+ setLogCount(payload.total || 0);
+ setActivePage(payload.page || 1);
+ setPageSize(payload.page_size || pageSize);
+ };
+
+ // Load logs function
+ const loadLogs = async (page = 1, size = pageSize) => {
+ setLoading(true);
+ const { channel_id, mj_id, start_timestamp, end_timestamp } = getFormValues();
+ let localStartTimestamp = Date.parse(start_timestamp);
+ let localEndTimestamp = Date.parse(end_timestamp);
+ const url = isAdminUser
+ ? `/api/mj/?p=${page}&page_size=${size}&channel_id=${channel_id}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`
+ : `/api/mj/self/?p=${page}&page_size=${size}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
+ const res = await API.get(url);
+ const { success, message, data } = res.data;
+ if (success) {
+ syncPageData(data);
+ } else {
+ showError(message);
+ }
+ setLoading(false);
+ };
+
+ // Page handlers
+ const handlePageChange = (page) => {
+ loadLogs(page, pageSize).then();
+ };
+
+ const handlePageSizeChange = async (size) => {
+ localStorage.setItem('mj-page-size', size + '');
+ await loadLogs(1, size);
+ };
+
+ // Refresh function
+ const refresh = async () => {
+ await loadLogs(1, pageSize);
+ };
+
+ // Copy text function
+ const copyText = async (text) => {
+ if (await copy(text)) {
+ showSuccess(t('已复制:') + text);
+ } else {
+ Modal.error({ title: t('无法复制到剪贴板,请手动复制'), content: text });
+ }
+ };
+
+ // Modal handlers
+ const openContentModal = (content) => {
+ setModalContent(content);
+ setIsModalOpen(true);
+ };
+
+ const openImageModal = (imageUrl) => {
+ setModalImageUrl(imageUrl);
+ setIsModalOpenurl(true);
+ };
+
+ // Initialize data
+ useEffect(() => {
+ const localPageSize = parseInt(localStorage.getItem('mj-page-size')) || ITEMS_PER_PAGE;
+ setPageSize(localPageSize);
+ loadLogs(1, localPageSize).then();
+ }, []);
+
+ return {
+ // Basic state
+ logs,
+ loading,
+ activePage,
+ logCount,
+ pageSize,
+ showBanner,
+ isAdminUser,
+
+ // Modal state
+ isModalOpen,
+ setIsModalOpen,
+ modalContent,
+ isModalOpenurl,
+ setIsModalOpenurl,
+ modalImageUrl,
+
+ // Form state
+ formApi,
+ setFormApi,
+ formInitValues,
+ getFormValues,
+
+ // Column visibility
+ visibleColumns,
+ showColumnSelector,
+ setShowColumnSelector,
+ handleColumnVisibilityChange,
+ handleSelectAll,
+ initDefaultColumns,
+ COLUMN_KEYS,
+
+ // Compact mode
+ compactMode,
+ setCompactMode,
+
+ // Functions
+ loadLogs,
+ handlePageChange,
+ handlePageSizeChange,
+ refresh,
+ copyText,
+ openContentModal,
+ openImageModal,
+ enrichLogs,
+ syncPageData,
+
+ // Translation
+ t,
+ };
+};
\ No newline at end of file
diff --git a/web/src/hooks/usage-logs/useUsageLogsData.js b/web/src/hooks/usage-logs/useUsageLogsData.js
index 326f6afc..5959714b 100644
--- a/web/src/hooks/usage-logs/useUsageLogsData.js
+++ b/web/src/hooks/usage-logs/useUsageLogsData.js
@@ -2,600 +2,600 @@ import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Modal } from '@douyinfe/semi-ui';
import {
- API,
- getTodayStartTimestamp,
- isAdmin,
- showError,
- showSuccess,
- timestamp2string,
- renderQuota,
- renderNumber,
- getLogOther,
- copy,
- renderClaudeLogContent,
- renderLogContent,
- renderAudioModelPrice,
- renderClaudeModelPrice,
- renderModelPrice
+ API,
+ getTodayStartTimestamp,
+ isAdmin,
+ showError,
+ showSuccess,
+ timestamp2string,
+ renderQuota,
+ renderNumber,
+ getLogOther,
+ copy,
+ renderClaudeLogContent,
+ renderLogContent,
+ renderAudioModelPrice,
+ renderClaudeModelPrice,
+ renderModelPrice
} from '../../helpers';
import { ITEMS_PER_PAGE } from '../../constants';
import { useTableCompactMode } from '../common/useTableCompactMode';
export const useLogsData = () => {
- const { t } = useTranslation();
+ const { t } = useTranslation();
- // Define column keys for selection
- const COLUMN_KEYS = {
- TIME: 'time',
- CHANNEL: 'channel',
- USERNAME: 'username',
- TOKEN: 'token',
- GROUP: 'group',
- TYPE: 'type',
- MODEL: 'model',
- USE_TIME: 'use_time',
- PROMPT: 'prompt',
- COMPLETION: 'completion',
- COST: 'cost',
- RETRY: 'retry',
- IP: 'ip',
- DETAILS: 'details',
- };
+ // Define column keys for selection
+ const COLUMN_KEYS = {
+ TIME: 'time',
+ CHANNEL: 'channel',
+ USERNAME: 'username',
+ TOKEN: 'token',
+ GROUP: 'group',
+ TYPE: 'type',
+ MODEL: 'model',
+ USE_TIME: 'use_time',
+ PROMPT: 'prompt',
+ COMPLETION: 'completion',
+ COST: 'cost',
+ RETRY: 'retry',
+ IP: 'ip',
+ DETAILS: 'details',
+ };
- // Basic state
- 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);
+ // Basic state
+ 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);
- // User and admin
- const isAdminUser = isAdmin();
+ // User and admin
+ const isAdminUser = isAdmin();
- // Statistics state
- const [stat, setStat] = useState({
- quota: 0,
- token: 0,
- });
+ // Statistics state
+ const [stat, setStat] = useState({
+ quota: 0,
+ token: 0,
+ });
- // Form state
- const [formApi, setFormApi] = useState(null);
- let now = new Date();
- const formInitValues = {
- username: '',
- token_name: '',
- model_name: '',
- channel: '',
- group: '',
- dateRange: [
- timestamp2string(getTodayStartTimestamp()),
- timestamp2string(now.getTime() / 1000 + 3600),
- ],
- logType: '0',
- };
+ // Form state
+ const [formApi, setFormApi] = useState(null);
+ let now = new Date();
+ const formInitValues = {
+ username: '',
+ token_name: '',
+ model_name: '',
+ channel: '',
+ group: '',
+ dateRange: [
+ timestamp2string(getTodayStartTimestamp()),
+ timestamp2string(now.getTime() / 1000 + 3600),
+ ],
+ logType: '0',
+ };
- // Column visibility state
- const [visibleColumns, setVisibleColumns] = useState({});
- const [showColumnSelector, setShowColumnSelector] = useState(false);
+ // Column visibility state
+ const [visibleColumns, setVisibleColumns] = useState({});
+ const [showColumnSelector, setShowColumnSelector] = useState(false);
- // Compact mode
- const [compactMode, setCompactMode] = useTableCompactMode('logs');
+ // Compact mode
+ const [compactMode, setCompactMode] = useTableCompactMode('logs');
- // User info modal state
- const [showUserInfo, setShowUserInfoModal] = useState(false);
- const [userInfoData, setUserInfoData] = useState(null);
+ // User info modal state
+ const [showUserInfo, setShowUserInfoModal] = useState(false);
+ const [userInfoData, setUserInfoData] = useState(null);
- // Load saved column preferences from localStorage
- useEffect(() => {
- const savedColumns = localStorage.getItem('logs-table-columns');
- if (savedColumns) {
- try {
- const parsed = JSON.parse(savedColumns);
- const defaults = getDefaultColumnVisibility();
- const merged = { ...defaults, ...parsed };
- setVisibleColumns(merged);
- } catch (e) {
- console.error('Failed to parse saved column preferences', e);
- initDefaultColumns();
- }
- } else {
- initDefaultColumns();
- }
- }, []);
+ // Load saved column preferences from localStorage
+ useEffect(() => {
+ const savedColumns = localStorage.getItem('logs-table-columns');
+ if (savedColumns) {
+ try {
+ const parsed = JSON.parse(savedColumns);
+ const defaults = getDefaultColumnVisibility();
+ const merged = { ...defaults, ...parsed };
+ setVisibleColumns(merged);
+ } catch (e) {
+ console.error('Failed to parse saved column preferences', e);
+ initDefaultColumns();
+ }
+ } else {
+ initDefaultColumns();
+ }
+ }, []);
- // Get default column visibility based on user role
- const getDefaultColumnVisibility = () => {
- return {
- [COLUMN_KEYS.TIME]: true,
- [COLUMN_KEYS.CHANNEL]: isAdminUser,
- [COLUMN_KEYS.USERNAME]: isAdminUser,
- [COLUMN_KEYS.TOKEN]: true,
- [COLUMN_KEYS.GROUP]: true,
- [COLUMN_KEYS.TYPE]: true,
- [COLUMN_KEYS.MODEL]: true,
- [COLUMN_KEYS.USE_TIME]: true,
- [COLUMN_KEYS.PROMPT]: true,
- [COLUMN_KEYS.COMPLETION]: true,
- [COLUMN_KEYS.COST]: true,
- [COLUMN_KEYS.RETRY]: isAdminUser,
- [COLUMN_KEYS.IP]: true,
- [COLUMN_KEYS.DETAILS]: true,
- };
- };
+ // Get default column visibility based on user role
+ const getDefaultColumnVisibility = () => {
+ return {
+ [COLUMN_KEYS.TIME]: true,
+ [COLUMN_KEYS.CHANNEL]: isAdminUser,
+ [COLUMN_KEYS.USERNAME]: isAdminUser,
+ [COLUMN_KEYS.TOKEN]: true,
+ [COLUMN_KEYS.GROUP]: true,
+ [COLUMN_KEYS.TYPE]: true,
+ [COLUMN_KEYS.MODEL]: true,
+ [COLUMN_KEYS.USE_TIME]: true,
+ [COLUMN_KEYS.PROMPT]: true,
+ [COLUMN_KEYS.COMPLETION]: true,
+ [COLUMN_KEYS.COST]: true,
+ [COLUMN_KEYS.RETRY]: isAdminUser,
+ [COLUMN_KEYS.IP]: true,
+ [COLUMN_KEYS.DETAILS]: true,
+ };
+ };
- // Initialize default column visibility
- const initDefaultColumns = () => {
- const defaults = getDefaultColumnVisibility();
- setVisibleColumns(defaults);
- localStorage.setItem('logs-table-columns', JSON.stringify(defaults));
- };
+ // Initialize default column visibility
+ const initDefaultColumns = () => {
+ const defaults = getDefaultColumnVisibility();
+ setVisibleColumns(defaults);
+ localStorage.setItem('logs-table-columns', JSON.stringify(defaults));
+ };
- // Handle column visibility change
- const handleColumnVisibilityChange = (columnKey, checked) => {
- const updatedColumns = { ...visibleColumns, [columnKey]: checked };
- setVisibleColumns(updatedColumns);
- };
+ // Handle column visibility change
+ const handleColumnVisibilityChange = (columnKey, checked) => {
+ const updatedColumns = { ...visibleColumns, [columnKey]: checked };
+ setVisibleColumns(updatedColumns);
+ };
- // Handle "Select All" checkbox
- const handleSelectAll = (checked) => {
- const allKeys = Object.keys(COLUMN_KEYS).map((key) => COLUMN_KEYS[key]);
- const updatedColumns = {};
+ // Handle "Select All" checkbox
+ const handleSelectAll = (checked) => {
+ const allKeys = Object.keys(COLUMN_KEYS).map((key) => COLUMN_KEYS[key]);
+ const updatedColumns = {};
- allKeys.forEach((key) => {
- if (
- (key === COLUMN_KEYS.CHANNEL ||
- key === COLUMN_KEYS.USERNAME ||
- key === COLUMN_KEYS.RETRY) &&
- !isAdminUser
- ) {
- updatedColumns[key] = false;
- } else {
- updatedColumns[key] = checked;
- }
- });
+ allKeys.forEach((key) => {
+ if (
+ (key === COLUMN_KEYS.CHANNEL ||
+ key === COLUMN_KEYS.USERNAME ||
+ key === COLUMN_KEYS.RETRY) &&
+ !isAdminUser
+ ) {
+ updatedColumns[key] = false;
+ } else {
+ updatedColumns[key] = checked;
+ }
+ });
- setVisibleColumns(updatedColumns);
- };
+ setVisibleColumns(updatedColumns);
+ };
- // Update table when column visibility changes
- useEffect(() => {
- if (Object.keys(visibleColumns).length > 0) {
- localStorage.setItem(
- 'logs-table-columns',
- JSON.stringify(visibleColumns),
- );
- }
- }, [visibleColumns]);
+ // Update table when column visibility changes
+ useEffect(() => {
+ if (Object.keys(visibleColumns).length > 0) {
+ localStorage.setItem(
+ 'logs-table-columns',
+ JSON.stringify(visibleColumns),
+ );
+ }
+ }, [visibleColumns]);
- // 获取表单值的辅助函数,确保所有值都是字符串
- const getFormValues = () => {
- const formValues = formApi ? formApi.getValues() : {};
+ // 获取表单值的辅助函数,确保所有值都是字符串
+ const getFormValues = () => {
+ const formValues = formApi ? formApi.getValues() : {};
- let start_timestamp = timestamp2string(getTodayStartTimestamp());
- let end_timestamp = timestamp2string(now.getTime() / 1000 + 3600);
+ let start_timestamp = timestamp2string(getTodayStartTimestamp());
+ let end_timestamp = timestamp2string(now.getTime() / 1000 + 3600);
- if (
- formValues.dateRange &&
- Array.isArray(formValues.dateRange) &&
- formValues.dateRange.length === 2
- ) {
- start_timestamp = formValues.dateRange[0];
- end_timestamp = formValues.dateRange[1];
- }
+ if (
+ formValues.dateRange &&
+ Array.isArray(formValues.dateRange) &&
+ formValues.dateRange.length === 2
+ ) {
+ start_timestamp = formValues.dateRange[0];
+ end_timestamp = formValues.dateRange[1];
+ }
- return {
- username: formValues.username || '',
- token_name: formValues.token_name || '',
- model_name: formValues.model_name || '',
- start_timestamp,
- end_timestamp,
- channel: formValues.channel || '',
- group: formValues.group || '',
- logType: formValues.logType ? parseInt(formValues.logType) : 0,
- };
- };
+ return {
+ username: formValues.username || '',
+ token_name: formValues.token_name || '',
+ model_name: formValues.model_name || '',
+ start_timestamp,
+ end_timestamp,
+ channel: formValues.channel || '',
+ group: formValues.group || '',
+ logType: formValues.logType ? parseInt(formValues.logType) : 0,
+ };
+ };
- // Statistics functions
- const getLogSelfStat = async () => {
- const {
- token_name,
- model_name,
- start_timestamp,
- end_timestamp,
- group,
- logType: formLogType,
- } = getFormValues();
- const currentLogType = formLogType !== undefined ? formLogType : logType;
- let localStartTimestamp = Date.parse(start_timestamp) / 1000;
- let localEndTimestamp = Date.parse(end_timestamp) / 1000;
- let url = `/api/log/self/stat?type=${currentLogType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&group=${group}`;
- url = encodeURI(url);
- let res = await API.get(url);
- const { success, message, data } = res.data;
- if (success) {
- setStat(data);
- } else {
- showError(message);
- }
- };
+ // Statistics functions
+ const getLogSelfStat = async () => {
+ const {
+ token_name,
+ model_name,
+ start_timestamp,
+ end_timestamp,
+ group,
+ logType: formLogType,
+ } = getFormValues();
+ const currentLogType = formLogType !== undefined ? formLogType : logType;
+ let localStartTimestamp = Date.parse(start_timestamp) / 1000;
+ let localEndTimestamp = Date.parse(end_timestamp) / 1000;
+ let url = `/api/log/self/stat?type=${currentLogType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&group=${group}`;
+ 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 () => {
- const {
- username,
- token_name,
- model_name,
- start_timestamp,
- end_timestamp,
- channel,
- group,
- logType: formLogType,
- } = getFormValues();
- const currentLogType = formLogType !== undefined ? formLogType : logType;
- let localStartTimestamp = Date.parse(start_timestamp) / 1000;
- let localEndTimestamp = Date.parse(end_timestamp) / 1000;
- let url = `/api/log/stat?type=${currentLogType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}&group=${group}`;
- 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 () => {
+ const {
+ username,
+ token_name,
+ model_name,
+ start_timestamp,
+ end_timestamp,
+ channel,
+ group,
+ logType: formLogType,
+ } = getFormValues();
+ const currentLogType = formLogType !== undefined ? formLogType : logType;
+ let localStartTimestamp = Date.parse(start_timestamp) / 1000;
+ let localEndTimestamp = Date.parse(end_timestamp) / 1000;
+ let url = `/api/log/stat?type=${currentLogType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}&group=${group}`;
+ 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 handleEyeClick = async () => {
+ if (loadingStat) {
+ return;
+ }
+ setLoadingStat(true);
+ if (isAdminUser) {
+ await getLogStat();
+ } else {
+ await getLogSelfStat();
+ }
+ setShowStat(true);
+ setLoadingStat(false);
+ };
- // User info function
- const showUserInfoFunc = async (userId) => {
- if (!isAdminUser) {
- return;
- }
- const res = await API.get(`/api/user/${userId}`);
- const { success, message, data } = res.data;
- if (success) {
- setUserInfoData(data);
- setShowUserInfoModal(true);
- } else {
- showError(message);
- }
- };
+ // User info function
+ const showUserInfoFunc = async (userId) => {
+ if (!isAdminUser) {
+ return;
+ }
+ const res = await API.get(`/api/user/${userId}`);
+ const { success, message, data } = res.data;
+ if (success) {
+ setUserInfoData(data);
+ setShowUserInfoModal(true);
+ } else {
+ showError(message);
+ }
+ };
- // Format logs data
- 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 = [];
+ // Format logs data
+ 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 (isAdminUser && (logs[i].type === 0 || logs[i].type === 2)) {
- expandDataLocal.push({
- key: t('渠道信息'),
- value: `${logs[i].channel} - ${logs[i].channel_name || '[未知]'}`,
- });
- }
- 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,
- });
- }
- if (other?.cache_tokens > 0) {
- expandDataLocal.push({
- key: t('缓存 Tokens'),
- value: other.cache_tokens,
- });
- }
- if (other?.cache_creation_tokens > 0) {
- expandDataLocal.push({
- key: t('缓存创建 Tokens'),
- value: other.cache_creation_tokens,
- });
- }
- if (logs[i].type === 2) {
- expandDataLocal.push({
- key: t('日志详情'),
- value: other?.claude
- ? renderClaudeLogContent(
- other?.model_ratio,
- other.completion_ratio,
- other.model_price,
- other.group_ratio,
- other?.user_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,
- 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) {
- let modelMapped =
- other?.is_model_mapped &&
- other?.upstream_model_name &&
- other?.upstream_model_name !== '';
- if (modelMapped) {
- expandDataLocal.push({
- key: t('请求并计费模型'),
- value: logs[i].model_name,
- });
- expandDataLocal.push({
- key: t('实际模型'),
- value: other.upstream_model_name,
- });
- }
- 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,
- other?.user_group_ratio,
- other?.cache_tokens || 0,
- other?.cache_ratio || 1.0,
- );
- } else if (other?.claude) {
- content = renderClaudeModelPrice(
- logs[i].prompt_tokens,
- logs[i].completion_tokens,
- other.model_ratio,
- other.model_price,
- other.completion_ratio,
- 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,
- );
- } else {
- content = renderModelPrice(
- logs[i].prompt_tokens,
- logs[i].completion_tokens,
- other?.model_ratio,
- other?.model_price,
- other?.completion_ratio,
- other?.group_ratio,
- other?.user_group_ratio,
- other?.cache_tokens || 0,
- other?.cache_ratio || 1.0,
- other?.image || false,
- other?.image_ratio || 0,
- other?.image_output || 0,
- other?.web_search || false,
- other?.web_search_call_count || 0,
- other?.web_search_price || 0,
- 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({
- key: t('计费过程'),
- value: content,
- });
- if (other?.reasoning_effort) {
- expandDataLocal.push({
- key: t('Reasoning Effort'),
- value: other.reasoning_effort,
- });
- }
- }
- expandDatesLocal[logs[i].key] = expandDataLocal;
- }
+ if (isAdminUser && (logs[i].type === 0 || logs[i].type === 2)) {
+ expandDataLocal.push({
+ key: t('渠道信息'),
+ value: `${logs[i].channel} - ${logs[i].channel_name || '[未知]'}`,
+ });
+ }
+ 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,
+ });
+ }
+ if (other?.cache_tokens > 0) {
+ expandDataLocal.push({
+ key: t('缓存 Tokens'),
+ value: other.cache_tokens,
+ });
+ }
+ if (other?.cache_creation_tokens > 0) {
+ expandDataLocal.push({
+ key: t('缓存创建 Tokens'),
+ value: other.cache_creation_tokens,
+ });
+ }
+ if (logs[i].type === 2) {
+ expandDataLocal.push({
+ key: t('日志详情'),
+ value: other?.claude
+ ? renderClaudeLogContent(
+ other?.model_ratio,
+ other.completion_ratio,
+ other.model_price,
+ other.group_ratio,
+ other?.user_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,
+ 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) {
+ let modelMapped =
+ other?.is_model_mapped &&
+ other?.upstream_model_name &&
+ other?.upstream_model_name !== '';
+ if (modelMapped) {
+ expandDataLocal.push({
+ key: t('请求并计费模型'),
+ value: logs[i].model_name,
+ });
+ expandDataLocal.push({
+ key: t('实际模型'),
+ value: other.upstream_model_name,
+ });
+ }
+ 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,
+ other?.user_group_ratio,
+ other?.cache_tokens || 0,
+ other?.cache_ratio || 1.0,
+ );
+ } else if (other?.claude) {
+ content = renderClaudeModelPrice(
+ logs[i].prompt_tokens,
+ logs[i].completion_tokens,
+ other.model_ratio,
+ other.model_price,
+ other.completion_ratio,
+ 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,
+ );
+ } else {
+ content = renderModelPrice(
+ logs[i].prompt_tokens,
+ logs[i].completion_tokens,
+ other?.model_ratio,
+ other?.model_price,
+ other?.completion_ratio,
+ other?.group_ratio,
+ other?.user_group_ratio,
+ other?.cache_tokens || 0,
+ other?.cache_ratio || 1.0,
+ other?.image || false,
+ other?.image_ratio || 0,
+ other?.image_output || 0,
+ other?.web_search || false,
+ other?.web_search_call_count || 0,
+ other?.web_search_price || 0,
+ 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({
+ key: t('计费过程'),
+ value: content,
+ });
+ if (other?.reasoning_effort) {
+ expandDataLocal.push({
+ key: t('Reasoning Effort'),
+ value: other.reasoning_effort,
+ });
+ }
+ }
+ expandDatesLocal[logs[i].key] = expandDataLocal;
+ }
- setExpandData(expandDatesLocal);
- setLogs(logs);
- };
+ setExpandData(expandDatesLocal);
+ setLogs(logs);
+ };
- // Load logs function
- const loadLogs = async (startIdx, pageSize, customLogType = null) => {
- setLoading(true);
+ // Load logs function
+ const loadLogs = async (startIdx, pageSize, customLogType = null) => {
+ setLoading(true);
- let url = '';
- const {
- username,
- token_name,
- model_name,
- start_timestamp,
- end_timestamp,
- channel,
- group,
- logType: formLogType,
- } = getFormValues();
+ let url = '';
+ const {
+ username,
+ token_name,
+ model_name,
+ start_timestamp,
+ end_timestamp,
+ channel,
+ group,
+ logType: formLogType,
+ } = getFormValues();
- const currentLogType =
- customLogType !== null
- ? customLogType
- : formLogType !== undefined
- ? formLogType
- : logType;
+ const currentLogType =
+ customLogType !== null
+ ? customLogType
+ : formLogType !== undefined
+ ? formLogType
+ : logType;
- 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=${currentLogType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}&group=${group}`;
- } else {
- url = `/api/log/self/?p=${startIdx}&page_size=${pageSize}&type=${currentLogType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&group=${group}`;
- }
- 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);
+ 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=${currentLogType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}&group=${group}`;
+ } else {
+ url = `/api/log/self/?p=${startIdx}&page_size=${pageSize}&type=${currentLogType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&group=${group}`;
+ }
+ 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);
- };
+ setLogsFormat(newPageData);
+ } else {
+ showError(message);
+ }
+ setLoading(false);
+ };
- // Page handlers
- const handlePageChange = (page) => {
- setActivePage(page);
- loadLogs(page, pageSize).then((r) => { });
- };
+ // Page handlers
+ const handlePageChange = (page) => {
+ setActivePage(page);
+ loadLogs(page, pageSize).then((r) => { });
+ };
- const handlePageSizeChange = async (size) => {
- localStorage.setItem('page-size', size + '');
- setPageSize(size);
- setActivePage(1);
- loadLogs(activePage, size)
- .then()
- .catch((reason) => {
- showError(reason);
- });
- };
+ const handlePageSizeChange = async (size) => {
+ localStorage.setItem('page-size', size + '');
+ setPageSize(size);
+ setActivePage(1);
+ loadLogs(activePage, size)
+ .then()
+ .catch((reason) => {
+ showError(reason);
+ });
+ };
- // Refresh function
- const refresh = async () => {
- setActivePage(1);
- handleEyeClick();
- await loadLogs(1, pageSize);
- };
+ // Refresh function
+ const refresh = async () => {
+ setActivePage(1);
+ handleEyeClick();
+ await loadLogs(1, pageSize);
+ };
- // Copy text function
- const copyText = async (e, text) => {
- e.stopPropagation();
- if (await copy(text)) {
- showSuccess('已复制:' + text);
- } else {
- Modal.error({ title: t('无法复制到剪贴板,请手动复制'), content: text });
- }
- };
+ // Copy text function
+ const copyText = async (e, text) => {
+ e.stopPropagation();
+ if (await copy(text)) {
+ showSuccess('已复制:' + text);
+ } else {
+ Modal.error({ title: t('无法复制到剪贴板,请手动复制'), content: text });
+ }
+ };
- // Initialize data
- useEffect(() => {
- const localPageSize =
- parseInt(localStorage.getItem('page-size')) || ITEMS_PER_PAGE;
- setPageSize(localPageSize);
- loadLogs(activePage, localPageSize)
- .then()
- .catch((reason) => {
- showError(reason);
- });
- }, []);
+ // Initialize data
+ useEffect(() => {
+ const localPageSize =
+ parseInt(localStorage.getItem('page-size')) || ITEMS_PER_PAGE;
+ setPageSize(localPageSize);
+ loadLogs(activePage, localPageSize)
+ .then()
+ .catch((reason) => {
+ showError(reason);
+ });
+ }, []);
- // Initialize statistics when formApi is available
- useEffect(() => {
- if (formApi) {
- handleEyeClick();
- }
- }, [formApi]);
+ // Initialize statistics when formApi is available
+ useEffect(() => {
+ if (formApi) {
+ handleEyeClick();
+ }
+ }, [formApi]);
- // Check if any record has expandable content
- const hasExpandableRows = () => {
- return logs.some(
- (log) => expandData[log.key] && expandData[log.key].length > 0,
- );
- };
+ // Check if any record has expandable content
+ const hasExpandableRows = () => {
+ return logs.some(
+ (log) => expandData[log.key] && expandData[log.key].length > 0,
+ );
+ };
- return {
- // Basic state
- logs,
- expandData,
- showStat,
- loading,
- loadingStat,
- activePage,
- logCount,
- pageSize,
- logType,
- stat,
- isAdminUser,
+ return {
+ // Basic state
+ logs,
+ expandData,
+ showStat,
+ loading,
+ loadingStat,
+ activePage,
+ logCount,
+ pageSize,
+ logType,
+ stat,
+ isAdminUser,
- // Form state
- formApi,
- setFormApi,
- formInitValues,
- getFormValues,
+ // Form state
+ formApi,
+ setFormApi,
+ formInitValues,
+ getFormValues,
- // Column visibility
- visibleColumns,
- showColumnSelector,
- setShowColumnSelector,
- handleColumnVisibilityChange,
- handleSelectAll,
- initDefaultColumns,
- COLUMN_KEYS,
+ // Column visibility
+ visibleColumns,
+ showColumnSelector,
+ setShowColumnSelector,
+ handleColumnVisibilityChange,
+ handleSelectAll,
+ initDefaultColumns,
+ COLUMN_KEYS,
- // Compact mode
- compactMode,
- setCompactMode,
+ // Compact mode
+ compactMode,
+ setCompactMode,
- // User info modal
- showUserInfo,
- setShowUserInfoModal,
- userInfoData,
- showUserInfoFunc,
+ // User info modal
+ showUserInfo,
+ setShowUserInfoModal,
+ userInfoData,
+ showUserInfoFunc,
- // Functions
- loadLogs,
- handlePageChange,
- handlePageSizeChange,
- refresh,
- copyText,
- handleEyeClick,
- setLogsFormat,
- hasExpandableRows,
- setLogType,
+ // Functions
+ loadLogs,
+ handlePageChange,
+ handlePageSizeChange,
+ refresh,
+ copyText,
+ handleEyeClick,
+ setLogsFormat,
+ hasExpandableRows,
+ setLogType,
- // Translation
- t,
- };
+ // Translation
+ t,
+ };
};
\ No newline at end of file