diff --git a/video-gen-app/src/App.tsx b/video-gen-app/src/App.tsx index 7eee9ca5..d1c13417 100644 --- a/video-gen-app/src/App.tsx +++ b/video-gen-app/src/App.tsx @@ -12,6 +12,7 @@ import GenerateConver from './pages/GenerateConver'; import InitialReplication from './pages/InitialReplication'; import RemoveLens from './pages/RemoveLens'; import GeneratedRecord from './pages/GeneratedRecord'; +import AuthorizationPage from './pages/AuthorizationPage'; @@ -93,6 +94,8 @@ const App = () => { } /> } /> } /> + } /> + diff --git a/video-gen-app/src/api/index.ts b/video-gen-app/src/api/index.ts index 9695d3ff..ccd66567 100644 --- a/video-gen-app/src/api/index.ts +++ b/video-gen-app/src/api/index.ts @@ -342,7 +342,12 @@ export async function gethistoryItems(Pagebreak: any): Promise { return api.get('/generation-ai/history/'+Pagebreak); } +// 删除ai对话历史记录 +export async function deleteHistory(id: string): Promise { + await api.delete(`/generation-ai/tasks/${id}`); +} - - +export async function calculateCredits(): Promise { + return api.get('/credits/credit-ratios'); +} diff --git a/video-gen-app/src/pages/AuthorizationPage.tsx b/video-gen-app/src/pages/AuthorizationPage.tsx new file mode 100644 index 00000000..210be13b --- /dev/null +++ b/video-gen-app/src/pages/AuthorizationPage.tsx @@ -0,0 +1,166 @@ +import React, { useEffect, useState, useLayoutEffect, useRef, useCallback } from 'react'; +import { Button, Table, Checkbox, Tag, Space, message } from 'antd'; +import { PlusOutlined, CheckCircleOutlined, ClockCircleOutlined, CiCircleOutlined } from '@ant-design/icons'; + +// 模拟授权数据 +const mockAuthorizations = [ + { id: '1867060028363785', status: 'active', description: '用户张三的API授权' }, + { id: '1867059757929740', status: 'pending', description: '用户李四的API授权' }, + { id: '1867059808785418', status: 'active', description: '用户王五的API授权' }, +]; + +// 状态配置 +const statusConfig = { + active: { label: '已授权', color: 'green', icon: CheckCircleOutlined }, + pending: { label: '待授权', color: 'gold', icon: ClockCircleOutlined }, + expired: { label: '已过期', color: 'red', icon: CiCircleOutlined }, + revoked: { label: '已撤销', color: 'gray', icon: CiCircleOutlined }, +}; + +const AuthorizationPage: React.FC = () => { + const [authorizations, setAuthorizations] = useState(mockAuthorizations); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [loading, setLoading] = useState(false); + + // 状态标签渲染 + const renderStatus = (status: string) => { + const config = statusConfig[status as keyof typeof statusConfig] || statusConfig.expired; + const Icon = config.icon; + return ( + + + {config.label} + + ); + }; + + // 表格列配置 + const columns = [ + // { + // title: '', + // dataIndex: 'selection', + // key: 'selection', + // width: 60, + // render: (_: any, record: typeof mockAuthorizations[0]) => ( + // { + // if (e.target.checked) { + // setSelectedRowKeys([...selectedRowKeys, record.id]); + // } else { + // setSelectedRowKeys(selectedRowKeys.filter(key => key !== record.id)); + // } + // }} + // /> + // ), + // }, + { + title: '序号', + dataIndex: 'index', + key: 'index', + width: 80, + render: (text: number) => {text}, + }, + { + title: '授权ID', + dataIndex: 'id', + key: 'id', + ellipsis: true, + render: (text: string) => ( + {text} + ), + }, + { + title: '授权状态', + dataIndex: 'status', + key: 'status', + width: 140, + render: (text: string) => renderStatus(text), + }, + ]; + + // 处理点击授权按钮 + const handleAuthorize = () => { + if (selectedRowKeys.length === 0) { + message.warning('请先选择需要授权的记录'); + return; + } + + setLoading(true); + // 模拟授权操作 + setTimeout(() => { + setAuthorizations(prev => + prev.map(item => + selectedRowKeys.includes(item.id) ? { ...item, status: 'active' } : item + ) + ); + setSelectedRowKeys([]); + setLoading(false); + message.success(`成功授权 ${selectedRowKeys.length} 条记录`); + }, 800); + }; + + // 准备表格数据(添加序号) + const tableData = authorizations.map((item, index) => ({ + ...item, + index: index + 1, + key: item.id, + })); + + return ( +
+ {/* 页面标题 */} +
+

+ 授权管理 +

+ {/*

管理系统授权信息,查看和操作授权状态

*/} +
+ + {/* 操作栏 */} +
+ +
+ + {/* 表格 */} +
+ `共 ${total} 条记录`, + }} + rowKey="id" + bordered={false} + style={{ padding: 16 }} + scroll={{ x: 'max-content' }} + /> + + + {/* 底部提示 */} + {/*
+ 提示:勾选记录后点击"点击授权"按钮可批量授权 +
*/} + + ); +}; + +export default AuthorizationPage; \ No newline at end of file diff --git a/video-gen-app/src/pages/GenerateConver.tsx b/video-gen-app/src/pages/GenerateConver.tsx index 9bc0d2b9..f212c77a 100644 --- a/video-gen-app/src/pages/GenerateConver.tsx +++ b/video-gen-app/src/pages/GenerateConver.tsx @@ -15,8 +15,10 @@ import { Modal, } from 'antd'; -import { getParameters, createGenerationTask, getgen_list,getEngine,uploadImage, - uploadVideo, getCreditRatios } from '../api'; +import { + getParameters, createGenerationTask, getgen_list, getEngine, uploadImage, + uploadVideo, getCreditRatios, deleteHistory,calculateCredits +} from '../api'; import { PlusOutlined, @@ -31,6 +33,9 @@ import { CaretDownOutlined, SwapOutlined, WarningOutlined, + SettingOutlined, + LayoutOutlined, + } from '@ant-design/icons'; const { Header, Sider, Content } = Layout; @@ -61,7 +66,7 @@ interface Message { ratio?: string; resolution?: string; timestamp?: string; - engine_id:string; + engine_id: string; } @@ -118,7 +123,7 @@ const AIChatPage: React.FC = () => { // 从URL中提取exp时间戳(支持相对路径和完整URL) const extractExpTimestamp = (url: string): number | null => { if (!url) return null; - + try { // 尝试作为完整URL解析 const urlObj = new URL(url); @@ -185,7 +190,7 @@ const AIChatPage: React.FC = () => { const [videoDuration, setVideoDuration] = useState(5); const [videoAspectRatio, setVideoAspectRatio] = useState('16:9'); const [videoResolution, setVideoResolution] = useState('720p'); - const [expandedEngine, setExpandedEngine] = useState(null); + const [showEngineModal, setShowEngineModal] = useState(false); const [engineOptions, setEngineOptions] = useState<{ ratios: string[]; resolutions: string[]; @@ -199,50 +204,32 @@ const AIChatPage: React.FC = () => { const [enginesele, setEnginesele] = useState([]); const [creditRatios, setCreditRatios] = useState([]); const [cimage, setCimage] = useState([]); + const [creditCalculationData, setCreditCalculationData] = useState([]); - // 计算视频积分 - const calcVideoCredits = (duration: number, resolution: string): number => { - for (let i = 0; i < creditRatios.length; i++) { - const ratio = creditRatios[i]; - if (ratio.resolution === resolution) { - const result = Math.round((ratio.baseCredits + ratio.perSecondCredits * duration) * ratio.ratio); - return result; - } - } - return 0; - }; - - // 计算图片积分 - const getImageCredits = (imageSize: string): number => { - - - // 将 imageSize 转换为 cimage 中的 resolution 格式 - const resolution = imageSize - - // 尝试精确匹配 - for (let i = 0; i < cimage.length; i++) { - const item = cimage[i]; - - if (item.resolution === resolution) { - return item.baseCredits; - } - } - - // 如果没有精确匹配,尝试查找第一个可用的积分配置 - // if (cimage.length > 0) { - // console.log('getImageCredits fallback to first item:', cimage[0].baseCredits); - // return cimage[0].baseCredits; - // } - - return 0; - }; - - // 获取预估积分 + // 获取预估积分 - 根据引擎ID、类型和分辨率计算 const getEstimatedCredits = (): number => { + // 根据当前选择的引擎ID、类型和分辨率查找对应的积分配置 + const config = creditCalculationData.find((item: any) => + item.modelConfigId === countType && + item.genType === mediaType && + item.resolution === (mediaType === 'video' ? videoResolution : selectedResolution) + ); + + if (!config) { + return 0; + } + + + console.log(config); + + + // 根据配置计算积分 if (mediaType === 'video') { - return calcVideoCredits(videoDuration, videoResolution); + // 视频:(秒数 × perSecondCredits + baseCredits) × ratio + return Math.round((videoDuration * config.perSecondCredits + config.baseCredits) * config.ratio); } else { - return getImageCredits(selectedResolution); + // 图片:baseCredits × ratio + return config.baseCredits * config.ratio; } }; @@ -269,11 +256,11 @@ const AIChatPage: React.FC = () => { const scrollToBottom = useCallback((behavior: ScrollBehavior = 'instant') => { const scrollContainer = scrollContainerRef.current; if (!scrollContainer) return; - + const doScroll = () => { scrollContainer.scrollTo({ top: scrollContainer.scrollHeight, behavior }); }; - + requestAnimationFrame(doScroll); }, []); @@ -296,25 +283,80 @@ const AIChatPage: React.FC = () => { if (!isValidOption) { setCountType(newOptions[0].id); } - + } }, [mediaType, enginesele]); + // 点击外部关闭弹窗 + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + const target = e.target as HTMLElement; + // 关闭引擎选择弹窗 + if (showEngineModal && !target.closest('.image-settings-popover') && !target.closest('.image-settings-trigger')) { + setShowEngineModal(false); + } + // 关闭图片设置弹窗 + if (showImageSettingsModal && !target.closest('.image-settings-popover') && !target.closest('.image-settings-trigger')) { + setShowImageSettingsModal(false); + } + // 关闭视频设置弹窗 + if (showVideoSettingsModal && !target.closest('.image-settings-popover') && !target.closest('.image-settings-trigger')) { + setShowVideoSettingsModal(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [showEngineModal, showImageSettingsModal, showVideoSettingsModal]); + // 初始化获取参数 - 只在组件挂载时执行一次 useEffect(() => { getEngine() - .then((data:any) => { + .then((data: any) => { + console.log('引擎', data); + setEnginesele(data.engine); + + // 如果是视频模式,初始化视频参数选项 + if (data.engine.video && data.engine.video.length > 0) { + const defaultEngine = data.engine.video[0]; + setEngineOptions({ + ratios: defaultEngine.supportedRatios || ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'], + resolutions: defaultEngine.supportedResolutions || ['480p', '720p', '1080p'], + durations: defaultEngine.supportedDurations || [5, 8, 10, 12, 15], + }); + // 设置默认选中值 + if (defaultEngine.supportedRatios?.length > 0) { + setVideoAspectRatio(defaultEngine.supportedRatios[0]); + } + if (defaultEngine.supportedResolutions?.length > 0) { + setVideoResolution(defaultEngine.supportedResolutions[0]); + } + if (defaultEngine.supportedDurations?.length > 0) { + setVideoDuration(defaultEngine.supportedDurations[0]); + } + // 设置默认引擎 + setCountType(defaultEngine.id); + } }) .catch(() => { }); getCreditRatios() .then((data: any) => { + console.log('积分', data); + setCreditRatios(data.video || []); setCimage(data.image || []); }) .catch((error) => { }); + calculateCredits().then((data: any) => { + console.log('积分计算', data); + // 保存积分计算数据 + setCreditCalculationData(data); + }) getgen_list(Pagebreak).then((data: any) => { let mess_list = data.items let total = data.total @@ -523,7 +565,7 @@ const AIChatPage: React.FC = () => { id: '', gen_type: mediaType, original_prompt: inputValue.trim(), - engine_id:countType, + engine_id: countType, idempotency_key: new Date().toLocaleString('zh-CN'), // 统一的媒体数组,包含 name、type、url @@ -541,16 +583,16 @@ const AIChatPage: React.FC = () => { resolution: videoResolution, }), }; - + // 设置加载状态 setLoading(true); - + createGenerationTask(newMessage).then(() => { // 创建任务成功后,清空输入框和已上传媒体 setInputValue(''); setCurrentMedia([]); - + // 创建任务成功后,重置页数为1,获取最新列表 const newPagebreak = { ...Pagebreak, page: 1 }; setPagebreak(newPagebreak); @@ -566,9 +608,9 @@ const AIChatPage: React.FC = () => { }).catch((error) => { }); }).catch((error: any) => { - + // 失败时不清空输入框和媒体 - + // 尝试从错误对象中提取detail字段 let errorMessage = "创建任务失败"; if (error?.response?.data?.detail) { @@ -580,7 +622,7 @@ const AIChatPage: React.FC = () => { } else if (error?.message) { errorMessage = error.message; } - + message.error({ content: errorMessage, duration: 3, @@ -750,7 +792,7 @@ const AIChatPage: React.FC = () => { // ==================== 渲染 ==================== return ( - + {/* 左侧边栏 - 对话列表(已隐藏,保留代码) */} {false && ( { )} {/* 遍历消息列表 */} - {gen_list.map((message) => ( + {(() => { + const msgApi = message; + return gen_list.map((msg) => (
{ padding: '12px 16px', width: '600px', boxShadow: '0 2px 8px rgba(0,0,0,0.06)', + position: 'relative', }} > - {/* 文本内容 */} -

- {message.originalPrompt} - {message.mediaReferences && message.mediaReferences.length > 0 && ( - { - e.stopPropagation(); - const target = e.currentTarget as HTMLElement; - const rect = target.getBoundingClientRect(); - setAttachmentPopupPosition({ - x: rect.left, - y: rect.top - 10 - }); - setAttachmentPopupMessageId(message.id); - setAttachmentPopupVisible(true); + {/* 删除按钮 - 右上角 */} +

+ { + await deleteHistory(msg.id); + msgApi.success('删除成功'); + // 直接在本地列表中删除对应数据 + setGen_list(prev => prev.filter(item => item.id !== msg.id)); + setTotalnumber(prev => prev - 1); + }} + okText="确定" + cancelText="取消" + > + + +
+ + {/* 文本内容 */} +

+ {msg.originalPrompt} +

+ + {/* 根据 status 显示不同内容 */} {/* 生成中 - 显示加载动画 */} - {message.status === 'generating' && ( + {msg.status === 'generating' && (
@@ -1008,27 +1082,27 @@ const AIChatPage: React.FC = () => {
)} {/* 生成失败 - 显示失败提示 */} - {message.status === 'failed' && ( + {msg.status === 'failed' && (
生成失败,请重试
)} {/* 已完成 - 显示媒体内容 */} - {message.status === 'completed' && ( + {msg.status === 'completed' && (
{ - setPreviewUrl(message.genType === 'image' ? message.imageUrl : message.videoUrl); - setPreviewType(message.genType === 'image' ? 'image' : 'video'); + setPreviewUrl(msg.genType === 'image' ? msg.imageUrl : msg.videoUrl); + setPreviewType(msg.genType === 'image' ? 'image' : 'video'); setPreviewVisible(true); }} - style={{ cursor: 'pointer', overflow: 'hidden', borderRadius: 8, position: 'relative' ,height: 200 }} + style={{ cursor: 'pointer', overflow: 'hidden', borderRadius: 8, position: 'relative', height: 200 }} > - {message.genType === 'image' ? ( + {msg.genType === 'image' ? ( {message.name} { e.currentTarget.style.transform = 'scale(1.05)'; }} onMouseLeave={(e) => { e.currentTarget.style.transform = 'scale(1)'; }} @@ -1036,8 +1110,8 @@ const AIChatPage: React.FC = () => { ) : ( <> {message.name} { e.currentTarget.style.transform = 'scale(1.05)'; }} onMouseLeave={(e) => { e.currentTarget.style.transform = 'scale(1)'; }} @@ -1059,7 +1133,7 @@ const AIChatPage: React.FC = () => { boxShadow: '0 4px 12px rgba(0,0,0,0.3)', }}> - +
@@ -1071,22 +1145,48 @@ const AIChatPage: React.FC = () => {
{/* 时间戳和参数信息 */} -
- {message.createdAt?.replace('T', ' ').split('.')[0]} - {message.genType === 'image' - ? `${message.imageProportion || ''} ${message.imagePx || ''} ${message.imageSize || ''}` - : `${message.duration || ''}s ${message.aspectRatio || ''} ${message.resolution || ''}` - } - {message.engineSnapshot.name} - 消耗积分:{message.creditsCost} - +
+ {msg.createdAt?.replace('T', ' ').split('.')[0]} + {/* 引擎标签 */} + + {/* */} + {msg.engineSnapshot.name} + + {/* 参数标签 */} + + {/* */} + {msg.genType === 'image' + ? `${msg.imageProportion || ''} · ${msg.imagePx || ''} · ${msg.imageSize || ''}` + : `${msg.duration || ''}s · ${msg.aspectRatio || ''} · ${msg.resolution || ''}` + } + + 消耗积分:{msg.creditsCost} + {msg.mediaReferences && msg.mediaReferences.length > 0 && ( + { + e.stopPropagation(); + const target = e.currentTarget as HTMLElement; + const rect = target.getBoundingClientRect(); + setAttachmentPopupPosition({ + x: rect.left, + y: rect.top - 10 + }); + setAttachmentPopupMessageId(msg.id); + setAttachmentPopupVisible(true); + }} + > + 附件详情 + + + )}
+
- ))} - + ))})()} {/* 消息列表底部标记 - 用于自动滚动 */} @@ -1128,74 +1228,74 @@ const AIChatPage: React.FC = () => { }} onClick={(e) => e.stopPropagation()} > -
- 附件列表 - -
- {gen_list.find((msg: any) => msg.id === attachmentPopupMessageId)?.mediaReferences?.map((ref: any, idx: number) => ( -
{ - setAttachmentPreviewUrl(ref.url); - setAttachmentPreviewType(ref.url.includes('.mp4') || ref.url.includes('.mov') || ref.url.includes('.avi') || ref.url.includes('.video') ? 'video' : 'image'); - setAttachmentPreviewName(ref.name); - setAttachmentPreviewVisible(true); - // 关闭附件悬浮窗,避免层级覆盖问题 - setAttachmentPopupVisible(false); - setAttachmentPopupMessageId(null); - }} - style={{ - display: 'flex', - alignItems: 'center', - gap: 8, - padding: '8px 12px', - borderRadius: 6, - cursor: 'pointer', - fontSize: 13, - color: '#333', - transition: 'background 0.2s' - }} - onMouseEnter={(e) => e.currentTarget.style.background = '#f5f5f5'} - onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'} - > - {ref.type === 'image' ? ( - - ) : ( - - )} - {ref.name} +
+ 附件列表 +
- ))} + {gen_list.find((msg: any) => msg.id === attachmentPopupMessageId)?.mediaReferences?.map((ref: any, idx: number) => ( +
{ + setAttachmentPreviewUrl(ref.url); + setAttachmentPreviewType(ref.url.includes('.mp4') || ref.url.includes('.mov') || ref.url.includes('.avi') || ref.url.includes('.video') ? 'video' : 'image'); + setAttachmentPreviewName(ref.name); + setAttachmentPreviewVisible(true); + // 关闭附件悬浮窗,避免层级覆盖问题 + setAttachmentPopupVisible(false); + setAttachmentPopupMessageId(null); + }} + style={{ + display: 'flex', + alignItems: 'center', + gap: 8, + padding: '8px 12px', + borderRadius: 6, + cursor: 'pointer', + fontSize: 13, + color: '#333', + transition: 'background 0.2s' + }} + onMouseEnter={(e) => e.currentTarget.style.background = '#f5f5f5'} + onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'} + > + {ref.type === 'image' ? ( + + ) : ( + + )} + {ref.name} +
+ ))}
)} @@ -1255,9 +1355,9 @@ const AIChatPage: React.FC = () => { )} {/* 输入框区域 */} -
{ onKeyPress={handleKeyPress} placeholder="上传最多4张参考图,输入提示词描述您想生成的画面..." autoSize={{ minRows: 1, maxRows: 4 }} - style={{ - flex: 1, - borderRadius: 12, - border: 'none', - outline: 'none', + style={{ + flex: 1, + borderRadius: 12, + border: 'none', + outline: 'none', boxShadow: 'none', fontSize: 14, lineHeight: 1.5, @@ -1335,7 +1435,7 @@ const AIChatPage: React.FC = () => { onClick={handleSend} disabled={!inputValue.trim() && currentMedia.length === 0} loading={loading} - style={{ + style={{ flexShrink: 0, width: 48, height: 48, @@ -1360,24 +1460,159 @@ const AIChatPage: React.FC = () => { border: '1px solid #e2e8f0', height: 28, }}> */} - {/* 类型 */} - + {/* 类型 */} + {/*
*/} + {/* 引擎选择器 */} +
+ + + {showEngineModal && ( +
e.stopPropagation()} + > + {/* 选择引擎 */} +
+ + 选择引擎 + +
+ {(mediaType === 'image' ? enginesele.image : enginesele.video)?.map((engine: any) => ( + + ))} +
+
+
+ )} +
+ {/* 图片设置按钮 */} {mediaType === 'image' && (
@@ -1385,34 +1620,27 @@ const AIChatPage: React.FC = () => { onClick={() => setShowImageSettingsModal(!showImageSettingsModal)} className="image-settings-trigger" style={{ - minWidth: 180, - padding: '0px 12px', - height: 28, + minWidth: 220, + padding: '4px 12px', + height: 34, borderRadius: 8, - border: '1px solid #e2e8f0', - backgroundColor: '#f8f9fc', + border: 'none', + backgroundColor: '#f1f5f9', cursor: 'pointer', - display: 'flex', + display: 'inline-flex', alignItems: 'center', - justifyContent: 'space-between', - gap: 8, + gap: 6, transition: 'all 0.2s', }} > -
- - {selectedRatio === 'auto' ? '智能' : selectedRatio} - - - {selectedResolution == '2K' ? '高清 2K' : '超清 4K'} | {width}×{height} - -
- + + + {selectedRatio === 'auto' ? '智能' : selectedRatio} · {selectedResolution == '2K' ? '2K高清' : '4K超清'} · {width}×{height} + {showImageSettingsModal && ( @@ -1673,34 +1901,27 @@ const AIChatPage: React.FC = () => { onClick={() => setShowVideoSettingsModal(!showVideoSettingsModal)} className="image-settings-trigger" style={{ - minWidth: 180, - padding: '0px 12px', - height: 28, + minWidth: 220, + padding: '4px 12px', + height: 34, borderRadius: 8, - border: '1px solid #e2e8f0', - backgroundColor: '#f8f9fc', + border: 'none', + backgroundColor: '#f1f5f9', cursor: 'pointer', - display: 'flex', + display: 'inline-flex', alignItems: 'center', - justifyContent: 'space-between', - gap: 8, + gap: 6, transition: 'all 0.2s', }} > -
- - {videoAspectRatio} - - - {videoDuration}s | {videoResolution} - -
- + + + {videoAspectRatio} · {videoDuration}s · {videoResolution} + {showVideoSettingsModal && ( @@ -1791,44 +2012,63 @@ const AIChatPage: React.FC = () => { }}> 选择时长 -
- {engineOptions.durations.map((duration) => ( - - ))} + /> +
+
+ + {videoDuration} + + +
@@ -1882,34 +2122,6 @@ const AIChatPage: React.FC = () => { )} )} - - {/* 引擎选择器 */} -
- 引擎 - -
- {/* 预估积分 */}
预估积分 @@ -1949,7 +2161,7 @@ const AIChatPage: React.FC = () => { 下载 , ]} - width={800} + width={800} centered closeIcon={
@@ -434,7 +434,7 @@ const GeneratedRecord: React.FC = () => { ); }; - + // 下载文件 const handleDownload = (item: any) => { const url = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${item.videoUrl || item.imageUrl}`; @@ -445,16 +445,16 @@ const GeneratedRecord: React.FC = () => { link.click(); document.body.removeChild(link); }; - + // 预览文件 const handlePreview = (item: any) => { - + setPreviewItem(item); setPreviewVisible(true); // 触发事件通知布局组件关闭浮动按钮 window.dispatchEvent(new Event('previewOpen')); }; - + // 关闭预览并暂停视频 const handleClosePreview = () => { // 方法1: 使用 ref @@ -470,7 +470,7 @@ const GeneratedRecord: React.FC = () => { }); setPreviewVisible(false); }; - + useEffect(() => { setLoading(true); let parameters = ''; @@ -490,7 +490,7 @@ const GeneratedRecord: React.FC = () => { } else { setRecordList(prev => [...prev, ...data]); } - + setTotalnumber(res?.totalDays || 0); }).catch((err) => { if (Pagebreak.page === 1) { @@ -509,16 +509,16 @@ const GeneratedRecord: React.FC = () => { page: prev.page + 1 })); - + }; // 分组加载更多 - const handleGroupLoadMore = async (time:string, date: string, page: number) => { + const handleGroupLoadMore = async (time: string, date: string, page: number) => { if (loadingGroups.has(date)) return; setLoadingGroups(prev => new Set([...prev, date])); - + const addpage = page + 1; - + let parameters = ``; if (filterType === 'project') { @@ -529,10 +529,10 @@ const GeneratedRecord: React.FC = () => { try { const res: any = await gethistoryItems(parameters); - + // gethistoryItems 返回数组,直接使用 const newItems: any[] = res.items || []; - + if (newItems && newItems.length > 0) { setRecordList(prev => prev.map(group => { if (group.generatedDate === time) { @@ -566,14 +566,14 @@ const GeneratedRecord: React.FC = () => { return (
{/* Header */} -
+ {/*
生成历史 查看所有生成的视频和图片记录 -
+
*/} {/* First row filter: 项目记录 / 创作记录 */}
{ : '#f8f9fc', border: filterMedia === 'video' ? 'none' : '1px solid #e2e8f0', color: filterMedia === 'video' ? '#fff' : '#64748b', - fontWeight: 600, + fontWeight: 600, }} icon={} > @@ -680,7 +680,7 @@ const GeneratedRecord: React.FC = () => { /> ) : (
- {recordlist.map((group: any,index: number) => ( + {recordlist.map((group: any, index: number) => (
{/* Date label */}
{ )}
)} - + {/* 预览弹窗 */} {previewVisible && previewItem && (
{ }} />
- + {/* 内容区域 */}
{ alignItems: 'center', }}> {/* 媒体预览 */} -
@@ -846,9 +846,9 @@ const GeneratedRecord: React.FC = () => { src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewItem.videoUrl}`} controls autoPlay - style={{ - maxWidth: '100%', - maxHeight: '55vh', + style={{ + maxWidth: '100%', + maxHeight: '55vh', borderRadius: 8, objectFit: 'contain', }} @@ -857,16 +857,16 @@ const GeneratedRecord: React.FC = () => { 预览 )}
- + {/* 参数信息 */}
{ 文件信息 - +
{/* ID */} {/*
@@ -891,7 +891,7 @@ const GeneratedRecord: React.FC = () => { {previewItem.id || '-'}
*/} - + {/* 类型 */}
类型 @@ -899,59 +899,59 @@ const GeneratedRecord: React.FC = () => { {filterMedia === 'video' ? '视频' : '图片'}
-
+
请求 {previewItem.originalPrompt}
- + {/* 分辨率 */} {filterMedia === 'image' && ( <> -
- 比例 - - {previewItem.imageProportion} - -
-
- 分辨率 - - {previewItem.imageSize} - -
-
- 尺寸 - - {previewItem.imagePx} - -
- - )} - {filterMedia === 'video' && ( +
+ 比例 + + {previewItem.imageProportion} + +
+
+ 分辨率 + + {previewItem.imageSize} + +
+
+ 尺寸 + + {previewItem.imagePx} + +
+ + )} + {filterMedia === 'video' && ( <> -
- 比例 - - {previewItem.aspectRatio} - -
-
- 分辨率 - - {previewItem.resolution} - -
- {/*
+
+ 比例 + + {previewItem.aspectRatio} + +
+
+ 分辨率 + + {previewItem.resolution} + +
+ {/*
尺寸 {previewItem.imagePx}
*/} - - )} - + + )} + {/* 时长(视频) */} {filterMedia === 'video' && (
@@ -961,7 +961,7 @@ const GeneratedRecord: React.FC = () => {
)} - + {/* 文件大小 */} {/*
文件大小 @@ -969,7 +969,7 @@ const GeneratedRecord: React.FC = () => { {previewItem.size ? formatFileSize(previewItem.size) : '-'}
*/} - + {/* 创建时间 */}
创建时间 @@ -977,7 +977,7 @@ const GeneratedRecord: React.FC = () => { {formatDateTime(previewItem.createdAt || previewItem.generatedDate)}
- + {/* 生成引擎 */}
生成引擎 @@ -986,7 +986,7 @@ const GeneratedRecord: React.FC = () => {
- + {/* 分隔线 */}
@@ -1038,29 +1038,42 @@ const GeneratedRecord: React.FC = () => { )} {/* 操作按钮 */} -
- - +
+
+ + +
+
+ +
+ +
@@ -1095,14 +1108,14 @@ function formatDateTime(dateString: string): string { if (!dateString) return '-'; const date = new Date(dateString); if (isNaN(date.getTime())) return '-'; - + const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); - + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } diff --git a/video-gen-app/src/pages/LoginPage.tsx b/video-gen-app/src/pages/LoginPage.tsx index 0b4930ef..d4732eb9 100644 --- a/video-gen-app/src/pages/LoginPage.tsx +++ b/video-gen-app/src/pages/LoginPage.tsx @@ -332,6 +332,7 @@ const LoginPage: React.FC = () => { + } placeholder="请设置密码(至少6位)" style={inputStyle} />