diff --git a/controller/midjourney.go b/controller/midjourney.go index a5ef8e4..cf5ce64 100644 --- a/controller/midjourney.go +++ b/controller/midjourney.go @@ -148,7 +148,16 @@ func GetAllMidjourney(c *gin.Context) { if p < 0 { p = 0 } - logs := model.GetAllTasks(p*common.ItemsPerPage, common.ItemsPerPage) + + // 解析其他查询参数 + queryParams := model.TaskQueryParams{ + ChannelID: c.Query("channel_id"), + MjID: c.Query("mj_id"), + StartTimestamp: c.Query("start_timestamp"), + EndTimestamp: c.Query("end_timestamp"), + } + + logs := model.GetAllTasks(p*common.ItemsPerPage, common.ItemsPerPage, queryParams) if logs == nil { logs = make([]*model.Midjourney, 0) } @@ -164,9 +173,17 @@ func GetUserMidjourney(c *gin.Context) { if p < 0 { p = 0 } + userId := c.GetInt("id") log.Printf("userId = %d \n", userId) - logs := model.GetAllUserTask(userId, p*common.ItemsPerPage, common.ItemsPerPage) + + queryParams := model.TaskQueryParams{ + MjID: c.Query("mj_id"), + StartTimestamp: c.Query("start_timestamp"), + EndTimestamp: c.Query("end_timestamp"), + } + + logs := model.GetAllUserTask(userId, p*common.ItemsPerPage, common.ItemsPerPage, queryParams) if logs == nil { logs = make([]*model.Midjourney, 0) } diff --git a/model/midjourney.go b/model/midjourney.go index c6e5c9a..6cc1b21 100644 --- a/model/midjourney.go +++ b/model/midjourney.go @@ -22,29 +22,68 @@ type Midjourney struct { ChannelId int `json:"channel_id"` } -func GetAllUserTask(userId int, startIdx int, num int) []*Midjourney { +// 用于包含所有搜索条件的结构体,可以根据需求添加更多字段 +type TaskQueryParams struct { + ChannelID string + MjID string + StartTimestamp string + EndTimestamp string +} + +func GetAllUserTask(userId int, startIdx int, num int, queryParams TaskQueryParams) []*Midjourney { var tasks []*Midjourney var err error - err = DB.Where("user_id = ?", userId).Order("id desc").Limit(num).Offset(startIdx).Find(&tasks).Error + + // 初始化查询构建器 + query := DB.Where("user_id = ?", userId) + + if queryParams.MjID != "" { + query = query.Where("mj_id = ?", queryParams.MjID) + } + if queryParams.StartTimestamp != "" { + // 假设您已将前端传来的时间戳转换为数据库所需的时间格式,并处理了时间戳的验证和解析 + query = query.Where("submit_time >= ?", queryParams.StartTimestamp) + } + if queryParams.EndTimestamp != "" { + query = query.Where("submit_time <= ?", queryParams.EndTimestamp) + } + + // 获取数据 + err = query.Order("id desc").Limit(num).Offset(startIdx).Find(&tasks).Error if err != nil { return nil } - for _, task := range tasks { - task.ImageUrl = common.ServerAddress + "/mj/image/" + task.MjId - } + return tasks } -func GetAllTasks(startIdx int, num int) []*Midjourney { +func GetAllTasks(startIdx int, num int, queryParams TaskQueryParams) []*Midjourney { var tasks []*Midjourney var err error - err = DB.Order("id desc").Limit(num).Offset(startIdx).Find(&tasks).Error + + // 初始化查询构建器 + query := DB + + // 添加过滤条件 + if queryParams.ChannelID != "" { + query = query.Where("channel_id = ?", queryParams.ChannelID) + } + if queryParams.MjID != "" { + query = query.Where("mj_id = ?", queryParams.MjID) + } + if queryParams.StartTimestamp != "" { + query = query.Where("submit_time >= ?", queryParams.StartTimestamp) + } + if queryParams.EndTimestamp != "" { + query = query.Where("submit_time <= ?", queryParams.EndTimestamp) + } + + // 获取数据 + err = query.Order("id desc").Limit(num).Offset(startIdx).Find(&tasks).Error if err != nil { return nil } - for _, task := range tasks { - task.ImageUrl = common.ServerAddress + "/mj/image/" + task.MjId - } + return tasks } diff --git a/web/src/components/MjLogsTable.js b/web/src/components/MjLogsTable.js index 4d070b0..e6b13c2 100644 --- a/web/src/components/MjLogsTable.js +++ b/web/src/components/MjLogsTable.js @@ -1,409 +1,432 @@ -import React, { useEffect, useState } from 'react'; -import { Button, Form, Header, Label, Pagination, Segment, Select, Table,Modal } from 'semantic-ui-react'; -import { API, isAdmin, showError, timestamp2string } from '../helpers'; +import React, {useEffect, useState} from 'react'; +import {Label} from 'semantic-ui-react'; +import {API, copy, isAdmin, showError, showSuccess, timestamp2string} from '../helpers'; -import { ITEMS_PER_PAGE } from '../constants'; -import { renderQuota } from '../helpers/render'; -import {Link} from "react-router-dom"; +import {Table, Avatar, Tag, Form, Button, Layout, Select, Popover, Modal } from '@douyinfe/semi-ui'; +import {ITEMS_PER_PAGE} from '../constants'; +import {renderNumber, renderQuota, stringToColor} from '../helpers/render'; -function renderTimestamp(timestamp) { - return ( - <> - {timestamp2string(timestamp)} - - ); -} -const MODE_OPTIONS = [ - { key: 'all', text: '全部用户', value: 'all' }, - { key: 'self', text: '当前用户', value: 'self' } -]; - -const LOG_OPTIONS = [ - { key: '0', text: '全部', value: 0 }, - // { key: '1', text: '绘图', value: 1 }, - // { key: '2', text: '放大', value: 2 }, - // { key: '3', text: '变换', value: 3 }, - // { key: '4', text: '图生文', value: 4 }, - // { key: '5', text: '图片混合', value: 5 } -]; +const colors = ['amber', 'blue', 'cyan', 'green', 'grey', 'indigo', + 'light-blue', 'lime', 'orange', 'pink', + 'purple', 'red', 'teal', 'violet', 'yellow' +] function renderType(type) { switch (type) { case 'IMAGINE': - return ; + return 绘图; case 'UPSCALE': - return ; + return 放大; case 'VARIATION': - return ; + return 变换; case 'DESCRIBE': - return ; + return 图生文; case 'BLEAND': - return ; + return 图混合; default: - return ; + return 未知; } } -function renderCode(type) { - switch (type) { + +function renderCode(code) { + switch (code) { case 1: - return ; + return 已提交; case 21: - return ; + return 排队中; case 22: - return ; + return 重复提交; default: - return ; + return 未知; } } + function renderStatus(type) { + // Ensure all cases are string literals by adding quotes. switch (type) { case 'SUCCESS': - return ; + return 成功; case 'NOT_START': - return ; + return 未启动; case 'SUBMITTED': - return ; + return 队列中; case 'IN_PROGRESS': - return ; + return 执行中; case 'FAILURE': - return ; + return 失败; default: - return ; + return 未知; } } +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}`; // 格式化输出 +}; + + + const LogsTable = () => { - const [logs, setLogs] = useState([ - - ]); - const [loading, setLoading] = useState(true); - const [activePage, setActivePage] = useState(1); - const [searchKeyword, setSearchKeyword] = useState(''); - const [searching, setSearching] = useState(false); - const [logType, setLogType] = useState(0); - const isAdminUser = isAdmin(); - - - let now = new Date(); - const [inputs, setInputs] = useState({ - username: '', - token_name: '', - model_name: '', - start_timestamp: timestamp2string(0), - end_timestamp: timestamp2string(now.getTime() / 1000 + 3600) - }); - const { username, token_name, model_name, start_timestamp, end_timestamp } = inputs; - - const [stat, setStat] = useState({ - quota: 0, - token: 0 - }); - - const [modalContent, setModalContent] = useState(''); - const [showModal, setShowModal] = useState(false); - - const showFullContent = (content) => { - setModalContent(content); - setShowModal(true); - }; - - - const loadLogs = async (startIdx) => { - let url = ''; - let localStartTimestamp = Date.parse(start_timestamp) / 1000; - let localEndTimestamp = Date.parse(end_timestamp) / 1000; - if (isAdminUser) { - url = `/api/mj/?p=${startIdx}&username=${username}&token_name=${token_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; - } else { - url = `/api/mj/self/?p=${startIdx}&token_name=${token_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; - } - const res = await API.get(url); - const { success, message, data } = res.data; - if (success) { - if (startIdx === 0) { - setLogs(data); - } else { - let newLogs = [...logs]; - newLogs.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data); - setLogs(newLogs); - } - } else { - showError(message); - } - setLoading(false); - }; - - const onPaginationChange = (e, { activePage }) => { - (async () => { - if (activePage === Math.ceil(logs.length / ITEMS_PER_PAGE) + 1) { - // In this case we have to load more data and then append them. - await loadLogs(activePage - 1); - } - setActivePage(activePage); - })(); - }; - - const refresh = async () => { - setLoading(true); - setActivePage(1) - await loadLogs(0); - // if (isAdminUser) { - // getLogStat().then(); - // } else { - // getLogSelfStat().then(); - // } - }; - - useEffect(() => { - refresh().then(); - }, [logType]); - - const searchLogs = async () => { - if (searchKeyword === '') { - // if keyword is blank, load files instead. - await loadLogs(0); - setActivePage(1); - return; - } - setSearching(true); - const res = await API.get(`/api/log/self/search?keyword=${searchKeyword}`); - const { success, message, data } = res.data; - if (success) { - setLogs(data); - setActivePage(1); - } else { - showError(message); - } - setSearching(false); - }; - - const handleKeywordChange = async (e, { value }) => { - setSearchKeyword(value.trim()); - }; - - const sortLog = (key) => { - if (logs.length === 0) return; - setLoading(true); - let sortedLogs = [...logs]; - if (typeof sortedLogs[0][key] === 'string'){ - sortedLogs.sort((a, b) => { - return ('' + a[key]).localeCompare(b[key]); - }); - } else { - sortedLogs.sort((a, b) => { - if (a[key] === b[key]) return 0; - if (a[key] > b[key]) return -1; - if (a[key] < b[key]) return 1; - }); - } - if (sortedLogs[0].id === logs[0].id) { - sortedLogs.reverse(); - } - setLogs(sortedLogs); - setLoading(false); - }; - - return ( - <> - - - - - { - sortLog('submit_time'); - }} - width={2} - > - 提交时间 - - { - sortLog('action'); - }} - width={1} - > - 类型 - - { - sortLog('mj_id'); - }} - width={2} - > - 任务ID - - { - sortLog('code'); - }} - width={1} - > - 提交结果 - - { - sortLog('status'); - }} - width={1} - > - 任务状态 - - { - sortLog('progress'); - }} - width={1} - > - 进度 - - { - sortLog('image_url'); - }} - width={1} - > - 结果图片 - - { - sortLog('prompt'); - }} - width={3} - > - Prompt - - { - sortLog('prompt_en'); - }} - width={3} - > - PromptEn - - { - sortLog('fail_reason'); - }} - width={1} - > - 失败原因 - - - - - - {logs - .slice( - (activePage - 1) * ITEMS_PER_PAGE, - activePage * ITEMS_PER_PAGE - ) - .map((log, idx) => { - if (log.deleted) return <>; + const [isModalOpen, setIsModalOpen] = useState(false); + const [modalContent, setModalContent] = useState(''); + const columns = [ + { + title: '提交时间', + dataIndex: 'submit_time', + render: (text, record, index) => { + return ( +
+ {renderTimestamp(text / 1000)} +
+ ); + }, + }, + { + title: '渠道', + dataIndex: 'channel_id', + className: isAdmin() ? 'tableShow' : 'tableHiddle', + render: (text, record, index) => { return ( - - {renderTimestamp(log.submit_time/1000)} - {/*{*/} - {/* isAdminUser && (*/} - {/* {log.username ? : ''}*/} - {/* )*/} - {/*}*/} - {renderType(log.action)} - {log.mj_id} - {renderCode(log.code)} - {renderStatus(log.status)} - {log.progress ? : ''} - - { - log.image_url ? ( - // 点击查看 - 点击查看 - ) : '暂未生成图片' - } - - - {log.prompt.length > 10 - ?
- {log.prompt.slice(0, 10)} - showFullContent(log.prompt)}>查看全部 -
- : log.prompt - } -
- - {log.prompt_en.length > 10 - ?
- {log.prompt_en.slice(0, 10)} - showFullContent(log.prompt_en)}>查看全部 -
- : log.prompt_en - } -
- - {log.fail_reason && log.fail_reason.length > 10 - ?
- {log.fail_reason.slice(0, 10)} - showFullContent(log.fail_reason)}>查看全部 -
- : log.fail_reason || '无' - } -
-
- ); - })} -
- - - -
-
- {/*Modal component goes here*/} - setShowModal(false)} centered> - -
{modalContent}
-
-
- - ); +
+ { + copyText(text); // 假设copyText是用于文本复制的函数 + }}> {text} +
+ + ); + }, + }, + { + title: '类型', + dataIndex: 'action', + render: (text, record, index) => { + return ( +
+ {renderType(text)} +
+ ); + }, + }, + { + title: '任务ID', + dataIndex: 'mj_id', + render: (text, record, index) => { + return ( +
+ {text} +
+ ); + }, + }, + { + title: '提交结果', + dataIndex: 'code', + className: isAdmin() ? 'tableShow' : 'tableHiddle', + render: (text, record, index) => { + return ( +
+ {renderCode(text)} +
+ ); + }, + }, + { + title: '任务状态', + dataIndex: 'status', + className: isAdmin() ? 'tableShow' : 'tableHiddle', + render: (text, record, index) => { + return ( +
+ {renderStatus(text)} +
+ ); + }, + }, + { + title: '进度', + dataIndex: 'progress', + render: (text, record, index) => { + return ( +
+ { {text} } +
+ ); + }, + }, + { + title: '结果图片', + dataIndex: 'image_url', + render: (text, record, index) => { + if (!text) { + return '无'; + } + return ( + + ); + } + }, + { + title: 'Prompt', + dataIndex: 'prompt', + render: (text, record, index) => { + // 如果text未定义,返回替代文本,例如空字符串''或其他 + if (!text) { + return '无'; + } + + return ( + text.length > 10 ? + <> + {text.slice(0, 10)} + + + : text + ); + } + }, + { + title: 'PromptEn', + dataIndex: 'prompt_en', + render: (text, record, index) => { + // 如果text未定义,返回替代文本,例如空字符串''或其他 + if (!text) { + return '无'; + } + + return ( + text.length > 10 ? + <> + {text.slice(0, 10)} + + + : text + ); + } + }, + { + title: '失败原因', + dataIndex: 'fail_reason', + render: (text, record, index) => { + // 如果text未定义,返回替代文本,例如空字符串''或其他 + if (!text) { + return '无'; + } + + return ( + text.length > 10 ? + <> + {text.slice(0, 10)} + + + : text + ); + } + } + + ]; + + const [logs, setLogs] = useState([]); + const [loading, setLoading] = useState(true); + const [activePage, setActivePage] = useState(1); + const [logCount, setLogCount] = useState(ITEMS_PER_PAGE); + const [logType, setLogType] = useState(0); + const isAdminUser = isAdmin(); + const [isModalOpenurl, setIsModalOpenurl] = useState(false); + + // 定义模态框图片URL的状态和更新函数 + const [modalImageUrl, setModalImageUrl] = useState(''); + let now = new Date(); + // 初始化start_timestamp为前一天 + const [inputs, setInputs] = useState({ + channel_id: '', + mj_id: '', + start_timestamp: timestamp2string(now.getTime() / 1000 - 2592000), + end_timestamp: timestamp2string(now.getTime() / 1000 + 3600), + }); + const {channel_id, mj_id, start_timestamp, end_timestamp} = inputs; + + const [stat, setStat] = useState({ + quota: 0, + token: 0 + }); + + const handleInputChange = (value, name) => { + setInputs((inputs) => ({...inputs, [name]: value})); + }; + + + + const setLogsFormat = (logs) => { + for (let i = 0; i < logs.length; i++) { + logs[i].timestamp2string = timestamp2string(logs[i].created_at); + logs[i].key = '' + logs[i].id; + } + // data.key = '' + data.id + setLogs(logs); + setLogCount(logs.length + ITEMS_PER_PAGE); + console.log(logCount); + } + + const loadLogs = async (startIdx) => { + setLoading(true); + + let url = ''; + let localStartTimestamp = Date.parse(start_timestamp); + let localEndTimestamp = Date.parse(end_timestamp); + if (isAdminUser) { + url = `/api/mj/?p=${startIdx}&channel_id=${channel_id}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; + } else { + url = `/api/mj/self/?p=${startIdx}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; + } + const res = await API.get(url); + const {success, message, data} = res.data; + if (success) { + if (startIdx === 0) { + setLogsFormat(data); + } else { + let newLogs = [...logs]; + newLogs.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data); + setLogsFormat(newLogs); + } + } else { + showError(message); + } + setLoading(false); + }; + + const pageData = logs.slice((activePage - 1) * ITEMS_PER_PAGE, activePage * ITEMS_PER_PAGE); + + const handlePageChange = page => { + setActivePage(page); + if (page === Math.ceil(logs.length / ITEMS_PER_PAGE) + 1) { + // In this case we have to load more data and then append them. + loadLogs(page - 1).then(r => { + }); + } + }; + + const refresh = async () => { + // setLoading(true); + setActivePage(1); + await loadLogs(0); + }; + + const copyText = async (text) => { + if (await copy(text)) { + showSuccess('已复制:' + text); + } else { + // setSearchKeyword(text); + Modal.error({title: '无法复制到剪贴板,请手动复制', content: text}); + } + } + + useEffect(() => { + refresh().then(); + }, [logType]); + + + + + return ( + <> + + +
+ <> + handleInputChange(value, 'channel_id')}/> + handleInputChange(value, 'mj_id')}/> + handleInputChange(value, 'start_timestamp')}/> + handleInputChange(value, 'end_timestamp')}/> + + + + + + + + setIsModalOpen(false)} + onCancel={() => setIsModalOpen(false)} + closable={null} + bodyStyle={{ height: '400px', overflow: 'auto' }} // 设置模态框内容区域样式 + width={800} // 设置模态框宽度 + > +

{modalContent}

+
+ {/* 模态框组件,用于展示图片 */} + setIsModalOpenurl(false)} + footer={null} // 模态框不显示底部按钮 + > + 结果图片 + + + + + ); }; export default LogsTable;