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, Card, Checkbox, Divider, Empty, Form, ImagePreview, Layout, Modal, Progress, Skeleton, Table, Tag, Typography } from '@douyinfe/semi-ui'; import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations'; import { ITEMS_PER_PAGE } from '../../constants'; import { IconEyeOpened, IconSearch, IconSetting } from '@douyinfe/semi-icons'; 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(); // 加载保存的列偏好设置 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 '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 任务记录')} )}
{/* 搜索表单区域 */}
setFormApi(api)} onSubmit={refresh} allowEmpty={true} autoComplete="off" layout="vertical" trigger="change" stopValidateWithError={false} >
{/* 时间选择器 */}
{/* 任务 ID */} } placeholder={t('任务 ID')} className="!rounded-full" showClear pure /> {/* 渠道 ID - 仅管理员可见 */} {isAdminUser && ( } placeholder={t('渠道 ID')} className="!rounded-full" showClear pure /> )}
{/* 操作按钮区域 */}
} shadows='always' bordered={false} > } darkModeImage={} description={t('搜索无结果')} style={{ padding: 30 }} /> } pagination={{ formatPageText: (page) => t('第 {{start}} - {{end}} 条,共 {{total}} 条', { start: page.currentStart, end: page.currentEnd, total: logCount, }), 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;