ai创建积分计算,授权页面添加
This commit is contained in:
@@ -12,6 +12,7 @@ import GenerateConver from './pages/GenerateConver';
|
|||||||
import InitialReplication from './pages/InitialReplication';
|
import InitialReplication from './pages/InitialReplication';
|
||||||
import RemoveLens from './pages/RemoveLens';
|
import RemoveLens from './pages/RemoveLens';
|
||||||
import GeneratedRecord from './pages/GeneratedRecord';
|
import GeneratedRecord from './pages/GeneratedRecord';
|
||||||
|
import AuthorizationPage from './pages/AuthorizationPage';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -93,6 +94,8 @@ const App = () => {
|
|||||||
<Route path="initial" element={<InitialReplication />} />
|
<Route path="initial" element={<InitialReplication />} />
|
||||||
<Route path="removelens" element={<RemoveLens />} />
|
<Route path="removelens" element={<RemoveLens />} />
|
||||||
<Route path="generated" element={<GeneratedRecord />} />
|
<Route path="generated" element={<GeneratedRecord />} />
|
||||||
|
<Route path="authorization" element={<AuthorizationPage />} />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -342,7 +342,12 @@ export async function gethistoryItems(Pagebreak: any): Promise<any[]> {
|
|||||||
return api.get('/generation-ai/history/'+Pagebreak);
|
return api.get('/generation-ai/history/'+Pagebreak);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除ai对话历史记录
|
||||||
|
export async function deleteHistory(id: string): Promise<void> {
|
||||||
|
await api.delete(`/generation-ai/tasks/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function calculateCredits(): Promise<any[]> {
|
||||||
|
return api.get('/credits/credit-ratios');
|
||||||
|
}
|
||||||
|
|||||||
@@ -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<string[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
// 状态标签渲染
|
||||||
|
const renderStatus = (status: string) => {
|
||||||
|
const config = statusConfig[status as keyof typeof statusConfig] || statusConfig.expired;
|
||||||
|
const Icon = config.icon;
|
||||||
|
return (
|
||||||
|
<Tag color={config.color} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||||
|
<Icon size={14} />
|
||||||
|
{config.label}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表格列配置
|
||||||
|
const columns = [
|
||||||
|
// {
|
||||||
|
// title: '',
|
||||||
|
// dataIndex: 'selection',
|
||||||
|
// key: 'selection',
|
||||||
|
// width: 60,
|
||||||
|
// render: (_: any, record: typeof mockAuthorizations[0]) => (
|
||||||
|
// <Checkbox
|
||||||
|
// checked={selectedRowKeys.includes(record.id)}
|
||||||
|
// onChange={(e) => {
|
||||||
|
// 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) => <span style={{ color: '#94a3b8' }}>{text}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '授权ID',
|
||||||
|
dataIndex: 'id',
|
||||||
|
key: 'id',
|
||||||
|
ellipsis: true,
|
||||||
|
render: (text: string) => (
|
||||||
|
<span style={{ fontWeight: 500, color: '#1e293b' }}>{text}</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 (
|
||||||
|
<div style={{ padding: 24, minHeight: '94vh', background: '#f8fafc' }}>
|
||||||
|
{/* 页面标题 */}
|
||||||
|
<div style={{ marginBottom: 20 }}>
|
||||||
|
<h1 style={{ fontSize: 24, fontWeight: 600, color: '#1e293b', marginBottom: 8 }}>
|
||||||
|
授权管理
|
||||||
|
</h1>
|
||||||
|
{/* <p style={{ color: '#64748b' }}>管理系统授权信息,查看和操作授权状态</p> */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 操作栏 */}
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 16 }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={handleAuthorize}
|
||||||
|
loading={loading}
|
||||||
|
// disabled={selectedRowKeys.length === 0}
|
||||||
|
style={{
|
||||||
|
height: 40,
|
||||||
|
padding: '0 24px',
|
||||||
|
borderRadius: 8,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
点击授权
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 表格 */}
|
||||||
|
<div style={{ background: '#fff', borderRadius: 12, boxShadow: '0 1px 3px rgba(0,0,0,0.05)' }}>
|
||||||
|
<Table
|
||||||
|
dataSource={tableData}
|
||||||
|
columns={columns}
|
||||||
|
pagination={{
|
||||||
|
pageSize: 10,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条记录`,
|
||||||
|
}}
|
||||||
|
rowKey="id"
|
||||||
|
bordered={false}
|
||||||
|
style={{ padding: 16 }}
|
||||||
|
scroll={{ x: 'max-content' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 底部提示 */}
|
||||||
|
{/* <div style={{ marginTop: 16, textAlign: 'center', color: '#94a3b8', fontSize: 13 }}>
|
||||||
|
提示:勾选记录后点击"点击授权"按钮可批量授权
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthorizationPage;
|
||||||
@@ -15,8 +15,10 @@ import {
|
|||||||
Modal,
|
Modal,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
|
|
||||||
import { getParameters, createGenerationTask, getgen_list,getEngine,uploadImage,
|
import {
|
||||||
uploadVideo, getCreditRatios } from '../api';
|
getParameters, createGenerationTask, getgen_list, getEngine, uploadImage,
|
||||||
|
uploadVideo, getCreditRatios, deleteHistory,calculateCredits
|
||||||
|
} from '../api';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
@@ -31,6 +33,9 @@ import {
|
|||||||
CaretDownOutlined,
|
CaretDownOutlined,
|
||||||
SwapOutlined,
|
SwapOutlined,
|
||||||
WarningOutlined,
|
WarningOutlined,
|
||||||
|
SettingOutlined,
|
||||||
|
LayoutOutlined,
|
||||||
|
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
const { Header, Sider, Content } = Layout;
|
const { Header, Sider, Content } = Layout;
|
||||||
@@ -61,7 +66,7 @@ interface Message {
|
|||||||
ratio?: string;
|
ratio?: string;
|
||||||
resolution?: string;
|
resolution?: string;
|
||||||
timestamp?: string;
|
timestamp?: string;
|
||||||
engine_id:string;
|
engine_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -185,7 +190,7 @@ const AIChatPage: React.FC = () => {
|
|||||||
const [videoDuration, setVideoDuration] = useState(5);
|
const [videoDuration, setVideoDuration] = useState(5);
|
||||||
const [videoAspectRatio, setVideoAspectRatio] = useState<string>('16:9');
|
const [videoAspectRatio, setVideoAspectRatio] = useState<string>('16:9');
|
||||||
const [videoResolution, setVideoResolution] = useState<string>('720p');
|
const [videoResolution, setVideoResolution] = useState<string>('720p');
|
||||||
const [expandedEngine, setExpandedEngine] = useState<string | null>(null);
|
const [showEngineModal, setShowEngineModal] = useState(false);
|
||||||
const [engineOptions, setEngineOptions] = useState<{
|
const [engineOptions, setEngineOptions] = useState<{
|
||||||
ratios: string[];
|
ratios: string[];
|
||||||
resolutions: string[];
|
resolutions: string[];
|
||||||
@@ -199,50 +204,32 @@ const AIChatPage: React.FC = () => {
|
|||||||
const [enginesele, setEnginesele] = useState<any>([]);
|
const [enginesele, setEnginesele] = useState<any>([]);
|
||||||
const [creditRatios, setCreditRatios] = useState<any[]>([]);
|
const [creditRatios, setCreditRatios] = useState<any[]>([]);
|
||||||
const [cimage, setCimage] = useState<any[]>([]);
|
const [cimage, setCimage] = useState<any[]>([]);
|
||||||
|
const [creditCalculationData, setCreditCalculationData] = useState<any[]>([]);
|
||||||
|
|
||||||
// 计算视频积分
|
// 获取预估积分 - 根据引擎ID、类型和分辨率计算
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取预估积分
|
|
||||||
const getEstimatedCredits = (): number => {
|
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') {
|
if (mediaType === 'video') {
|
||||||
return calcVideoCredits(videoDuration, videoResolution);
|
// 视频:(秒数 × perSecondCredits + baseCredits) × ratio
|
||||||
|
return Math.round((videoDuration * config.perSecondCredits + config.baseCredits) * config.ratio);
|
||||||
} else {
|
} else {
|
||||||
return getImageCredits(selectedResolution);
|
// 图片:baseCredits × ratio
|
||||||
|
return config.baseCredits * config.ratio;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -300,21 +287,76 @@ const AIChatPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [mediaType, enginesele]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
getEngine()
|
getEngine()
|
||||||
.then((data:any) => {
|
.then((data: any) => {
|
||||||
|
console.log('引擎', data);
|
||||||
|
|
||||||
setEnginesele(data.engine);
|
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(() => {
|
.catch(() => {
|
||||||
});
|
});
|
||||||
getCreditRatios()
|
getCreditRatios()
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
|
console.log('积分', data);
|
||||||
|
|
||||||
setCreditRatios(data.video || []);
|
setCreditRatios(data.video || []);
|
||||||
setCimage(data.image || []);
|
setCimage(data.image || []);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
});
|
});
|
||||||
|
calculateCredits().then((data: any) => {
|
||||||
|
console.log('积分计算', data);
|
||||||
|
// 保存积分计算数据
|
||||||
|
setCreditCalculationData(data);
|
||||||
|
})
|
||||||
getgen_list(Pagebreak).then((data: any) => {
|
getgen_list(Pagebreak).then((data: any) => {
|
||||||
let mess_list = data.items
|
let mess_list = data.items
|
||||||
let total = data.total
|
let total = data.total
|
||||||
@@ -523,7 +565,7 @@ const AIChatPage: React.FC = () => {
|
|||||||
id: '',
|
id: '',
|
||||||
gen_type: mediaType,
|
gen_type: mediaType,
|
||||||
original_prompt: inputValue.trim(),
|
original_prompt: inputValue.trim(),
|
||||||
engine_id:countType,
|
engine_id: countType,
|
||||||
|
|
||||||
idempotency_key: new Date().toLocaleString('zh-CN'),
|
idempotency_key: new Date().toLocaleString('zh-CN'),
|
||||||
// 统一的媒体数组,包含 name、type、url
|
// 统一的媒体数组,包含 name、type、url
|
||||||
@@ -750,7 +792,7 @@ const AIChatPage: React.FC = () => {
|
|||||||
// ==================== 渲染 ====================
|
// ==================== 渲染 ====================
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ minHeight: '95vh', background: '#fafafa' }}>
|
<Layout style={{ height: '94vh', background: '#fafafa', overflow: 'hidden' }}>
|
||||||
{/* 左侧边栏 - 对话列表(已隐藏,保留代码) */}
|
{/* 左侧边栏 - 对话列表(已隐藏,保留代码) */}
|
||||||
{false && (
|
{false && (
|
||||||
<Sider
|
<Sider
|
||||||
@@ -930,9 +972,11 @@ const AIChatPage: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* 遍历消息列表 */}
|
{/* 遍历消息列表 */}
|
||||||
{gen_list.map((message) => (
|
{(() => {
|
||||||
|
const msgApi = message;
|
||||||
|
return gen_list.map((msg) => (
|
||||||
<div
|
<div
|
||||||
key={message.id}
|
key={msg.id}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'flex-start', // 所有消息左对齐
|
justifyContent: 'flex-start', // 所有消息左对齐
|
||||||
@@ -967,33 +1011,63 @@ const AIChatPage: React.FC = () => {
|
|||||||
padding: '12px 16px',
|
padding: '12px 16px',
|
||||||
width: '600px',
|
width: '600px',
|
||||||
boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
|
boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
|
||||||
|
position: 'relative',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* 文本内容 */}
|
{/* 删除按钮 - 右上角 */}
|
||||||
<p style={{ margin: 0, fontSize: 14, color: '#333' }}>
|
<div style={{ position: 'absolute', top: 8, right: 8 }}>
|
||||||
{message.originalPrompt}
|
<Popconfirm
|
||||||
{message.mediaReferences && message.mediaReferences.length > 0 && (
|
title="确定要删除吗?"
|
||||||
<span
|
onConfirm={async () => {
|
||||||
style={{ marginLeft: 20, color: '#6366f1', cursor: 'pointer' }}
|
await deleteHistory(msg.id);
|
||||||
onClick={(e) => {
|
msgApi.success('删除成功');
|
||||||
e.stopPropagation();
|
// 直接在本地列表中删除对应数据
|
||||||
const target = e.currentTarget as HTMLElement;
|
setGen_list(prev => prev.filter(item => item.id !== msg.id));
|
||||||
const rect = target.getBoundingClientRect();
|
setTotalnumber(prev => prev - 1);
|
||||||
setAttachmentPopupPosition({
|
}}
|
||||||
x: rect.left,
|
okText="确定"
|
||||||
y: rect.top - 10
|
cancelText="取消"
|
||||||
});
|
>
|
||||||
setAttachmentPopupMessageId(message.id);
|
<button
|
||||||
setAttachmentPopupVisible(true);
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
style={{
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
borderRadius: 8,
|
||||||
|
border: 'none',
|
||||||
|
background: 'rgba(0,0,0,0.05)',
|
||||||
|
color: '#999',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
padding: 0,
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.background = '#fff2f0';
|
||||||
|
e.currentTarget.style.color = '#ff4d4f';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.background = 'rgba(0,0,0,0.05)';
|
||||||
|
e.currentTarget.style.color = '#999';
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
附件详情
|
<DeleteOutlined style={{ fontSize: 12 }} />
|
||||||
</span>
|
</button>
|
||||||
)}
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 文本内容 */}
|
||||||
|
<p style={{ margin: 0, fontSize: 14, color: '#333' }}>
|
||||||
|
{msg.originalPrompt}
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
{/* 根据 status 显示不同内容 */}
|
{/* 根据 status 显示不同内容 */}
|
||||||
{/* 生成中 - 显示加载动画 */}
|
{/* 生成中 - 显示加载动画 */}
|
||||||
{message.status === 'generating' && (
|
{msg.status === 'generating' && (
|
||||||
<div style={{ display: 'flex', justifyContent: 'flex-start', marginBottom: 16, marginTop: 12 }}>
|
<div style={{ display: 'flex', justifyContent: 'flex-start', marginBottom: 16, marginTop: 12 }}>
|
||||||
<div style={{ background: '#fff', borderRadius: '16px 16px 16px 4px', padding: '12px 16px', boxShadow: '0 2px 8px rgba(0,0,0,0.06)' }}>
|
<div style={{ background: '#fff', borderRadius: '16px 16px 16px 4px', padding: '12px 16px', boxShadow: '0 2px 8px rgba(0,0,0,0.06)' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
@@ -1008,27 +1082,27 @@ const AIChatPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* 生成失败 - 显示失败提示 */}
|
{/* 生成失败 - 显示失败提示 */}
|
||||||
{message.status === 'failed' && (
|
{msg.status === 'failed' && (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10, marginTop: 12, padding: '12px', background: '#fff2f0', borderRadius: 8 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10, marginTop: 12, padding: '12px', background: '#fff2f0', borderRadius: 8 }}>
|
||||||
<WarningOutlined style={{ color: '#ff4d4f', fontSize: 16 }} />
|
<WarningOutlined style={{ color: '#ff4d4f', fontSize: 16 }} />
|
||||||
<span style={{ fontSize: 14, color: '#ff4d4f' }}>生成失败,请重试</span>
|
<span style={{ fontSize: 14, color: '#ff4d4f' }}>生成失败,请重试</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* 已完成 - 显示媒体内容 */}
|
{/* 已完成 - 显示媒体内容 */}
|
||||||
{message.status === 'completed' && (
|
{msg.status === 'completed' && (
|
||||||
<div style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(150px, 1fr))', gap: 8, marginBottom: 10, marginTop: 12, width: '100%', }}>
|
<div style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(150px, 1fr))', gap: 8, marginBottom: 10, marginTop: 12, width: '100%', }}>
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPreviewUrl(message.genType === 'image' ? message.imageUrl : message.videoUrl);
|
setPreviewUrl(msg.genType === 'image' ? msg.imageUrl : msg.videoUrl);
|
||||||
setPreviewType(message.genType === 'image' ? 'image' : 'video');
|
setPreviewType(msg.genType === 'image' ? 'image' : 'video');
|
||||||
setPreviewVisible(true);
|
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' ? (
|
||||||
<img
|
<img
|
||||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}/static${message.imageUrl}&w=300&p=50`}
|
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}/static${msg.imageUrl}&w=300&p=50`}
|
||||||
alt={message.name}
|
alt={msg.name}
|
||||||
style={{ width: '100%', height: '100%', borderRadius: 8, objectFit: 'contain', backgroundColor: '#f1f5f9', transition: 'transform 0.2s' }}
|
style={{ width: '100%', height: '100%', borderRadius: 8, objectFit: 'contain', backgroundColor: '#f1f5f9', transition: 'transform 0.2s' }}
|
||||||
onMouseEnter={(e) => { e.currentTarget.style.transform = 'scale(1.05)'; }}
|
onMouseEnter={(e) => { e.currentTarget.style.transform = 'scale(1.05)'; }}
|
||||||
onMouseLeave={(e) => { e.currentTarget.style.transform = 'scale(1)'; }}
|
onMouseLeave={(e) => { e.currentTarget.style.transform = 'scale(1)'; }}
|
||||||
@@ -1036,8 +1110,8 @@ const AIChatPage: React.FC = () => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<img
|
<img
|
||||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}/static${message.videoCoverUrl}&w=300&p=50`}
|
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}/static${msg.videoCoverUrl}&w=300&p=50`}
|
||||||
alt={message.name}
|
alt={msg.name}
|
||||||
style={{ width: '100%', height: '100%', borderRadius: 8, objectFit: 'contain', backgroundColor: '#f1f5f9', transition: 'transform 0.2s' }}
|
style={{ width: '100%', height: '100%', borderRadius: 8, objectFit: 'contain', backgroundColor: '#f1f5f9', transition: 'transform 0.2s' }}
|
||||||
onMouseEnter={(e) => { e.currentTarget.style.transform = 'scale(1.05)'; }}
|
onMouseEnter={(e) => { e.currentTarget.style.transform = 'scale(1.05)'; }}
|
||||||
onMouseLeave={(e) => { e.currentTarget.style.transform = 'scale(1)'; }}
|
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)',
|
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
|
||||||
}}>
|
}}>
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="#fff">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="#fff">
|
||||||
<path d="M8 5v14l11-7z"/>
|
<path d="M8 5v14l11-7z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -1071,22 +1145,48 @@ const AIChatPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
{/* 时间戳和参数信息 */}
|
{/* 时间戳和参数信息 */}
|
||||||
|
|
||||||
<div style={{ margin: 4, fontSize: 11, color: '#999', textAlign: 'left' }}>
|
<div style={{ margin: 4, fontSize: 11, color: '#999', textAlign: 'left', display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' }}>
|
||||||
<span>{message.createdAt?.replace('T', ' ').split('.')[0]}</span>
|
<span>{msg.createdAt?.replace('T', ' ').split('.')[0]}</span>
|
||||||
<span style={{ marginLeft: 8 }}>{message.genType === 'image'
|
{/* 引擎标签 */}
|
||||||
? `${message.imageProportion || ''} ${message.imagePx || ''} ${message.imageSize || ''}`
|
<span >
|
||||||
: `${message.duration || ''}s ${message.aspectRatio || ''} ${message.resolution || ''}`
|
{/* <SettingsOutlined style={{ fontSize: 12 }} /> */}
|
||||||
}</span>
|
{msg.engineSnapshot.name}
|
||||||
<span style={{ marginLeft: 20 }}>{message.engineSnapshot.name}</span>
|
</span>
|
||||||
<span style={{ marginLeft: 20 }}>消耗积分:{message.creditsCost}</span>
|
{/* 参数标签 */}
|
||||||
|
<span >
|
||||||
|
{/* <LayoutGridOutlined style={{ fontSize: 12 }} /> */}
|
||||||
|
{msg.genType === 'image'
|
||||||
|
? `${msg.imageProportion || ''} · ${msg.imagePx || ''} · ${msg.imageSize || ''}`
|
||||||
|
: `${msg.duration || ''}s · ${msg.aspectRatio || ''} · ${msg.resolution || ''}`
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span style={{ marginLeft: 8 }}>消耗积分:{msg.creditsCost}</span>
|
||||||
|
{msg.mediaReferences && msg.mediaReferences.length > 0 && (
|
||||||
|
<span
|
||||||
|
style={{ marginLeft: 20, color: '#6366f1', cursor: 'pointer' }}
|
||||||
|
onClick={(e) => {
|
||||||
|
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);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
附件详情
|
||||||
|
</span>
|
||||||
|
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))})()}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* 消息列表底部标记 - 用于自动滚动 */}
|
{/* 消息列表底部标记 - 用于自动滚动 */}
|
||||||
@@ -1128,74 +1228,74 @@ const AIChatPage: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
|
||||||
<span style={{ fontSize: 14, fontWeight: 500 }}>附件列表</span>
|
<span style={{ fontSize: 14, fontWeight: 500 }}>附件列表</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAttachmentPopupVisible(false);
|
setAttachmentPopupVisible(false);
|
||||||
setAttachmentPopupMessageId(null);
|
setAttachmentPopupMessageId(null);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
background: '#ff4d4f',
|
background: '#ff4d4f',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
transition: 'all 0.2s',
|
transition: 'all 0.2s',
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.currentTarget.style.background = '#ff7875';
|
e.currentTarget.style.background = '#ff7875';
|
||||||
e.currentTarget.style.transform = 'scale(1.1)';
|
e.currentTarget.style.transform = 'scale(1.1)';
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.currentTarget.style.background = '#ff4d4f';
|
e.currentTarget.style.background = '#ff4d4f';
|
||||||
e.currentTarget.style.transform = 'scale(1)';
|
e.currentTarget.style.transform = 'scale(1)';
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
{gen_list.find((msg: any) => msg.id === attachmentPopupMessageId)?.mediaReferences?.map((ref: any, idx: number) => (
|
|
||||||
<div
|
|
||||||
key={idx}
|
|
||||||
onClick={() => {
|
|
||||||
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' ? (
|
|
||||||
<PictureOutlined style={{ color: '#3b82f6', fontSize: 14 }} />
|
|
||||||
) : (
|
|
||||||
<VideoCameraOutlined style={{ color: '#f59e0b', fontSize: 14 }} />
|
|
||||||
)}
|
|
||||||
{ref.name}
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
{gen_list.find((msg: any) => msg.id === attachmentPopupMessageId)?.mediaReferences?.map((ref: any, idx: number) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
onClick={() => {
|
||||||
|
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' ? (
|
||||||
|
<PictureOutlined style={{ color: '#3b82f6', fontSize: 14 }} />
|
||||||
|
) : (
|
||||||
|
<VideoCameraOutlined style={{ color: '#f59e0b', fontSize: 14 }} />
|
||||||
|
)}
|
||||||
|
{ref.name}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -1360,24 +1460,159 @@ const AIChatPage: React.FC = () => {
|
|||||||
border: '1px solid #e2e8f0',
|
border: '1px solid #e2e8f0',
|
||||||
height: 28,
|
height: 28,
|
||||||
}}> */}
|
}}> */}
|
||||||
{/* <Text style={{ fontSize: 12, color: '#94a3b8' }}>类型</Text> */}
|
{/* <Text style={{ fontSize: 12, color: '#94a3b8' }}>类型</Text> */}
|
||||||
<Select
|
<Select
|
||||||
value={mediaType}
|
value={mediaType}
|
||||||
onChange={(val) => setMediaType(val)}
|
onChange={(val) => setMediaType(val)}
|
||||||
style={{ width: 90,outline:'none',background: '#f8f9fc',border: '1px solid #e2e8f0', }}
|
style={{
|
||||||
size="small"
|
width: 100,
|
||||||
>
|
outline: 'none',
|
||||||
<Option value="image">
|
background: '#f1f5f9',
|
||||||
<PictureOutlined style={{ marginRight: 4 }} />
|
border: 'none',
|
||||||
图片
|
borderRadius: 8,
|
||||||
</Option>
|
height: 34,
|
||||||
<Option value="video">
|
}}
|
||||||
<VideoCameraOutlined style={{ marginRight: 4 }} />
|
size="middle"
|
||||||
视频
|
>
|
||||||
</Option>
|
<Option value="image">
|
||||||
</Select>
|
<PictureOutlined style={{ marginRight: 6, fontSize: 14 }} />
|
||||||
|
<span style={{ fontSize: 14, fontWeight: 500, color: '#64748b' }}>图片</span>
|
||||||
|
</Option>
|
||||||
|
<Option value="video">
|
||||||
|
<VideoCameraOutlined style={{ marginRight: 6, fontSize: 14 }} />
|
||||||
|
<span style={{ fontSize: 14, fontWeight: 500, color: '#64748b' }}>视频</span>
|
||||||
|
</Option>
|
||||||
|
</Select>
|
||||||
{/* </div> */}
|
{/* </div> */}
|
||||||
|
|
||||||
|
{/* 引擎选择器 */}
|
||||||
|
<div style={{ position: 'relative', display: 'inline-block' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowEngineModal(true)}
|
||||||
|
className="image-settings-trigger"
|
||||||
|
style={{
|
||||||
|
minWidth: 200,
|
||||||
|
padding: '4px 12px',
|
||||||
|
height: 34,
|
||||||
|
borderRadius: 8,
|
||||||
|
border: 'none',
|
||||||
|
backgroundColor: '#f1f5f9',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SettingOutlined style={{ fontSize: 14, color: '#64748b' }} />
|
||||||
|
<Text style={{
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#64748b',
|
||||||
|
}}>
|
||||||
|
{mediaType === 'image' ? enginesele.image?.find((e: any) => e.id === countType)?.name : enginesele.video?.find((e: any) => e.id === countType)?.name || '选择引擎'}
|
||||||
|
</Text>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showEngineModal && (
|
||||||
|
<div
|
||||||
|
className="image-settings-popover"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 'calc(100% + 8px)',
|
||||||
|
left: 0,
|
||||||
|
width: 360,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 16,
|
||||||
|
boxShadow: '0 10px 40px rgba(0,0,0,0.15)',
|
||||||
|
padding: 16,
|
||||||
|
border: 'none',
|
||||||
|
zIndex: 9999,
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{/* 选择引擎 */}
|
||||||
|
<div style={{ marginBottom: 8 }}>
|
||||||
|
<Text style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择引擎
|
||||||
|
</Text>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 4,
|
||||||
|
}}>
|
||||||
|
{(mediaType === 'image' ? enginesele.image : enginesele.video)?.map((engine: any) => (
|
||||||
|
<button
|
||||||
|
key={engine.id}
|
||||||
|
onClick={() => {
|
||||||
|
setCountType(engine.id);
|
||||||
|
// 根据选中的引擎更新视频参数选项
|
||||||
|
if (mediaType === 'video') {
|
||||||
|
setEngineOptions({
|
||||||
|
ratios: engine.supportedRatios || ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'],
|
||||||
|
resolutions: engine.supportedResolutions || ['480p', '720p', '1080p'],
|
||||||
|
durations: engine.supportedDurations || [5, 8, 10, 12, 15],
|
||||||
|
});
|
||||||
|
// 更新默认选中值,确保在新引擎支持的范围内
|
||||||
|
if (!engine.supportedRatios?.includes(videoAspectRatio)) {
|
||||||
|
setVideoAspectRatio(engine.supportedRatios?.[0] || '16:9');
|
||||||
|
}
|
||||||
|
if (!engine.supportedResolutions?.includes(videoResolution)) {
|
||||||
|
setVideoResolution(engine.supportedResolutions?.[0] || '720p');
|
||||||
|
}
|
||||||
|
if (!engine.supportedDurations?.includes(videoDuration)) {
|
||||||
|
setVideoDuration(engine.supportedDurations?.[0] || 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setShowEngineModal(false);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
minHeight: 48,
|
||||||
|
borderRadius: 8,
|
||||||
|
border: countType === engine.id
|
||||||
|
? '2px solid #6366f1'
|
||||||
|
: '1px solid #e5e7eb',
|
||||||
|
backgroundColor: countType === engine.id
|
||||||
|
? '#fff'
|
||||||
|
: '#f9fafb',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
padding: '8px 12px',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
textAlign: 'left',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: countType === engine.id ? 600 : 500,
|
||||||
|
color: countType === engine.id ? '#6366f1' : '#4b5563',
|
||||||
|
marginBottom: 2,
|
||||||
|
}}>
|
||||||
|
{engine.name}
|
||||||
|
</span>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: '#9ca3af',
|
||||||
|
}}>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 图片设置按钮 */}
|
{/* 图片设置按钮 */}
|
||||||
{mediaType === 'image' && (
|
{mediaType === 'image' && (
|
||||||
<div style={{ position: 'relative', display: 'inline-block' }}>
|
<div style={{ position: 'relative', display: 'inline-block' }}>
|
||||||
@@ -1385,34 +1620,27 @@ const AIChatPage: React.FC = () => {
|
|||||||
onClick={() => setShowImageSettingsModal(!showImageSettingsModal)}
|
onClick={() => setShowImageSettingsModal(!showImageSettingsModal)}
|
||||||
className="image-settings-trigger"
|
className="image-settings-trigger"
|
||||||
style={{
|
style={{
|
||||||
minWidth: 180,
|
minWidth: 220,
|
||||||
padding: '0px 12px',
|
padding: '4px 12px',
|
||||||
height: 28,
|
height: 34,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
border: '1px solid #e2e8f0',
|
border: 'none',
|
||||||
backgroundColor: '#f8f9fc',
|
backgroundColor: '#f1f5f9',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
display: 'flex',
|
display: 'inline-flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
gap: 6,
|
||||||
gap: 8,
|
|
||||||
transition: 'all 0.2s',
|
transition: 'all 0.2s',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ textAlign: 'left' }}>
|
<LayoutOutlined style={{ fontSize: 14, color: '#64748b' }} />
|
||||||
<Text style={{
|
<Text style={{
|
||||||
fontSize: 12,
|
fontSize: 14,
|
||||||
fontWeight: 600,
|
fontWeight: 500,
|
||||||
color: '#374151',
|
color: '#64748b',
|
||||||
marginRight: 8,
|
}}>
|
||||||
}}>
|
{selectedRatio === 'auto' ? '智能' : selectedRatio} · {selectedResolution == '2K' ? '2K高清' : '4K超清'} · {width}×{height}
|
||||||
{selectedRatio === 'auto' ? '智能' : selectedRatio}
|
</Text>
|
||||||
</Text>
|
|
||||||
<Text style={{ fontSize: 11, color: '#9ca3af' }}>
|
|
||||||
{selectedResolution == '2K' ? '高清 2K' : '超清 4K'} | {width}×{height}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<CaretDownOutlined style={{ fontSize: 12, color: '#9ca3af' }} />
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{showImageSettingsModal && (
|
{showImageSettingsModal && (
|
||||||
@@ -1673,34 +1901,27 @@ const AIChatPage: React.FC = () => {
|
|||||||
onClick={() => setShowVideoSettingsModal(!showVideoSettingsModal)}
|
onClick={() => setShowVideoSettingsModal(!showVideoSettingsModal)}
|
||||||
className="image-settings-trigger"
|
className="image-settings-trigger"
|
||||||
style={{
|
style={{
|
||||||
minWidth: 180,
|
minWidth: 220,
|
||||||
padding: '0px 12px',
|
padding: '4px 12px',
|
||||||
height: 28,
|
height: 34,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
border: '1px solid #e2e8f0',
|
border: 'none',
|
||||||
backgroundColor: '#f8f9fc',
|
backgroundColor: '#f1f5f9',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
display: 'flex',
|
display: 'inline-flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
gap: 6,
|
||||||
gap: 8,
|
|
||||||
transition: 'all 0.2s',
|
transition: 'all 0.2s',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ textAlign: 'left' }}>
|
<LayoutOutlined style={{ fontSize: 14, color: '#64748b' }} />
|
||||||
<Text style={{
|
<Text style={{
|
||||||
fontSize: 12,
|
fontSize: 14,
|
||||||
fontWeight: 600,
|
fontWeight: 500,
|
||||||
color: '#374151',
|
color: '#64748b',
|
||||||
marginRight: 8,
|
}}>
|
||||||
}}>
|
{videoAspectRatio} · {videoDuration}s · {videoResolution}
|
||||||
{videoAspectRatio}
|
</Text>
|
||||||
</Text>
|
|
||||||
<Text style={{ fontSize: 11, color: '#9ca3af' }}>
|
|
||||||
{videoDuration}s | {videoResolution}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<CaretDownOutlined style={{ fontSize: 12, color: '#9ca3af' }} />
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{showVideoSettingsModal && (
|
{showVideoSettingsModal && (
|
||||||
@@ -1791,44 +2012,63 @@ const AIChatPage: React.FC = () => {
|
|||||||
}}>
|
}}>
|
||||||
选择时长
|
选择时长
|
||||||
</Text>
|
</Text>
|
||||||
<div style={{
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
display: 'flex',
|
<div style={{ flex: 1, position: 'relative', height: 24, display: 'flex', alignItems: 'center' }}>
|
||||||
flexWrap: 'wrap',
|
{/* 背景轨道 */}
|
||||||
gap: 4,
|
<div style={{
|
||||||
}}>
|
position: 'absolute',
|
||||||
{engineOptions.durations.map((duration) => (
|
top: '50%',
|
||||||
<button
|
left: 0,
|
||||||
key={duration}
|
right: 0,
|
||||||
onClick={() => setVideoDuration(duration)}
|
height: 6,
|
||||||
|
borderRadius: 3,
|
||||||
|
background: '#e5e7eb',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
}} />
|
||||||
|
{/* 已滑动部分 */}
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: 0,
|
||||||
|
height: 6,
|
||||||
|
borderRadius: 3,
|
||||||
|
background: '#6366f1',
|
||||||
|
width: `${((videoDuration - Math.min(...engineOptions.durations)) / (Math.max(...engineOptions.durations) - Math.min(...engineOptions.durations))) * 100}%`,
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
}} />
|
||||||
|
{/* 滑块 */}
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={Math.min(...engineOptions.durations)}
|
||||||
|
max={Math.max(...engineOptions.durations)}
|
||||||
|
value={videoDuration}
|
||||||
|
onChange={(e) => setVideoDuration(Number(e.target.value))}
|
||||||
style={{
|
style={{
|
||||||
flex: '0 0 calc(20% - 4px)',
|
position: 'relative',
|
||||||
minWidth: 50,
|
width: '100%',
|
||||||
height: 42,
|
height: 24,
|
||||||
borderRadius: 6,
|
borderRadius: 3,
|
||||||
border: videoDuration === duration
|
background: 'transparent',
|
||||||
? '2px solid #6366f1'
|
outline: 'none',
|
||||||
: '1px solid #e5e7eb',
|
appearance: 'none',
|
||||||
backgroundColor: videoDuration === duration
|
|
||||||
? '#6366f1'
|
|
||||||
: '#f9fafb',
|
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
display: 'flex',
|
zIndex: 1,
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
transition: 'all 0.2s',
|
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<span style={{
|
</div>
|
||||||
fontSize: 12,
|
<div style={{
|
||||||
fontWeight: 600,
|
display: 'flex',
|
||||||
color: videoDuration === duration
|
alignItems: 'center',
|
||||||
? '#fff'
|
gap: 4,
|
||||||
: '#4b5563',
|
padding: '4px 12px',
|
||||||
}}>
|
backgroundColor: '#f1f5f9',
|
||||||
{duration}s
|
borderRadius: 6,
|
||||||
</span>
|
}}>
|
||||||
</button>
|
<span style={{ fontSize: 14, fontWeight: 600, color: '#64748b' }}>
|
||||||
))}
|
{videoDuration}
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: 12, color: '#9ca3af' }}>秒</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1882,34 +2122,6 @@ const AIChatPage: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 引擎选择器 */}
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 6,
|
|
||||||
padding: '5px 12px',
|
|
||||||
borderRadius: 8,
|
|
||||||
background: '#f8f9fc',
|
|
||||||
border: '1px solid #e2e8f0',
|
|
||||||
height: 28,
|
|
||||||
}}>
|
|
||||||
<Text style={{ fontSize: 12, color: '#94a3b8' }}>引擎</Text>
|
|
||||||
<Select
|
|
||||||
placeholder="选择引擎"
|
|
||||||
value={countType}
|
|
||||||
onChange={(val) => {
|
|
||||||
setCountType(val);
|
|
||||||
}}
|
|
||||||
style={{ width: 200 ,outline:'none',background: '#f8f9fc',border: '1px solid #e2e8f0', }}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{(mediaType === 'image' ? enginesele.image : enginesele.video)?.map((item:any) => (
|
|
||||||
<Option key={item.id} value={item.id}>{item.name}</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 预估积分 */}
|
{/* 预估积分 */}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||||
<Text style={{ fontSize: 12, color: '#94a3b8' }}>预估积分</Text>
|
<Text style={{ fontSize: 12, color: '#94a3b8' }}>预估积分</Text>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
XOutlined,
|
XOutlined,
|
||||||
ClockCircleOutlined,
|
ClockCircleOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { gethistory,gethistoryItems } from '../api';
|
import { gethistory, gethistoryItems } from '../api';
|
||||||
|
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
@@ -421,12 +421,12 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
opacity: 0,
|
opacity: 0,
|
||||||
transition: 'opacity 0.2s',
|
transition: 'opacity 0.2s',
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
(e.currentTarget as HTMLElement).style.opacity = '1';
|
(e.currentTarget as HTMLElement).style.opacity = '1';
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
(e.currentTarget as HTMLElement).style.opacity = '0';
|
(e.currentTarget as HTMLElement).style.opacity = '0';
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
点击预览
|
点击预览
|
||||||
</div>
|
</div>
|
||||||
@@ -513,7 +513,7 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 分组加载更多
|
// 分组加载更多
|
||||||
const handleGroupLoadMore = async (time:string, date: string, page: number) => {
|
const handleGroupLoadMore = async (time: string, date: string, page: number) => {
|
||||||
if (loadingGroups.has(date)) return;
|
if (loadingGroups.has(date)) return;
|
||||||
setLoadingGroups(prev => new Set([...prev, date]));
|
setLoadingGroups(prev => new Set([...prev, date]));
|
||||||
|
|
||||||
@@ -566,14 +566,14 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div style={{ marginBottom: 20 }}>
|
{/* <div style={{ marginBottom: 20 }}>
|
||||||
<Typography.Title level={3} style={{ margin: '0 0 4px', color: '#1a1a2e', fontWeight: 700 }}>
|
<Typography.Title level={3} style={{ margin: '0 0 4px', color: '#1a1a2e', fontWeight: 700 }}>
|
||||||
生成历史
|
生成历史
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
<Typography.Text style={{ color: '#94a3b8', fontSize: 14 }}>
|
<Typography.Text style={{ color: '#94a3b8', fontSize: 14 }}>
|
||||||
查看所有生成的视频和图片记录
|
查看所有生成的视频和图片记录
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
{/* First row filter: 项目记录 / 创作记录 */}
|
{/* First row filter: 项目记录 / 创作记录 */}
|
||||||
<div style={{
|
<div style={{
|
||||||
@@ -680,7 +680,7 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ padding: '0 4px' }}>
|
<div style={{ padding: '0 4px' }}>
|
||||||
{recordlist.map((group: any,index: number) => (
|
{recordlist.map((group: any, index: number) => (
|
||||||
<div key={index} style={{ marginBottom: 32 }}>
|
<div key={index} style={{ marginBottom: 32 }}>
|
||||||
{/* Date label */}
|
{/* Date label */}
|
||||||
<div style={{
|
<div style={{
|
||||||
@@ -899,7 +899,7 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
{filterMedia === 'video' ? '视频' : '图片'}
|
{filterMedia === 'video' ? '视频' : '图片'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 12 }}>
|
<div style={{ display: 'flex', gap: 12 }}>
|
||||||
<span style={{ color: '#94a3b8', fontSize: 13, flexShrink: 0, width: 40 }}>请求</span>
|
<span style={{ color: '#94a3b8', fontSize: 13, flexShrink: 0, width: 40 }}>请求</span>
|
||||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500, flex: 1, wordBreak: 'break-all' }}>
|
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500, flex: 1, wordBreak: 'break-all' }}>
|
||||||
{previewItem.originalPrompt}
|
{previewItem.originalPrompt}
|
||||||
@@ -909,48 +909,48 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
{/* 分辨率 */}
|
{/* 分辨率 */}
|
||||||
{filterMedia === 'image' && (
|
{filterMedia === 'image' && (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>比例</span>
|
<span style={{ color: '#94a3b8', fontSize: 13 }}>比例</span>
|
||||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||||
{previewItem.imageProportion}
|
{previewItem.imageProportion}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>分辨率</span>
|
<span style={{ color: '#94a3b8', fontSize: 13 }}>分辨率</span>
|
||||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||||
{previewItem.imageSize}
|
{previewItem.imageSize}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>尺寸</span>
|
<span style={{ color: '#94a3b8', fontSize: 13 }}>尺寸</span>
|
||||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||||
{previewItem.imagePx}
|
{previewItem.imagePx}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{filterMedia === 'video' && (
|
{filterMedia === 'video' && (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>比例</span>
|
<span style={{ color: '#94a3b8', fontSize: 13 }}>比例</span>
|
||||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||||
{previewItem.aspectRatio}
|
{previewItem.aspectRatio}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>分辨率</span>
|
<span style={{ color: '#94a3b8', fontSize: 13 }}>分辨率</span>
|
||||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||||
{previewItem.resolution}
|
{previewItem.resolution}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/* <div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
{/* <div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>尺寸</span>
|
<span style={{ color: '#94a3b8', fontSize: 13 }}>尺寸</span>
|
||||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||||
{previewItem.imagePx}
|
{previewItem.imagePx}
|
||||||
</span>
|
</span>
|
||||||
</div> */}
|
</div> */}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 时长(视频) */}
|
{/* 时长(视频) */}
|
||||||
{filterMedia === 'video' && (
|
{filterMedia === 'video' && (
|
||||||
@@ -1038,29 +1038,42 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{/* 操作按钮 */}
|
{/* 操作按钮 */}
|
||||||
<div style={{ display: 'flex', gap: 12 ,marginTop:20}}>
|
<div >
|
||||||
<Button
|
<div style={{ display: 'flex', gap: 12, marginTop: 20 }}>
|
||||||
type="primary"
|
<Button
|
||||||
icon={<DownloadOutlined />}
|
type="primary"
|
||||||
onClick={() => {
|
icon={<DownloadOutlined />}
|
||||||
// 暂停视频
|
onClick={() => {
|
||||||
if (videoRef.current) {
|
// 暂停视频
|
||||||
videoRef.current.pause();
|
if (videoRef.current) {
|
||||||
}
|
videoRef.current.pause();
|
||||||
const url = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewItem.videoUrl || previewItem.imageUrl}`;
|
}
|
||||||
window.open(url, '_blank');
|
const url = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewItem.videoUrl || previewItem.imageUrl}`;
|
||||||
}}
|
window.open(url, '_blank');
|
||||||
style={{ flex: 1, borderRadius: 8 }}
|
}}
|
||||||
disabled={isMediaExpired(previewItem.videoUrl || previewItem.imageUrl)}
|
style={{ flex: 1, borderRadius: 8 }}
|
||||||
>
|
disabled={isMediaExpired(previewItem.videoUrl || previewItem.imageUrl)}
|
||||||
{isMediaExpired(previewItem.videoUrl || previewItem.imageUrl) ? '资源已过期' : '下载'}
|
>
|
||||||
</Button>
|
{isMediaExpired(previewItem.videoUrl || previewItem.imageUrl) ? '资源已过期' : '下载'}
|
||||||
<Button
|
</Button>
|
||||||
onClick={handleClosePreview}
|
<Button
|
||||||
style={{ flex: 1, borderRadius: 8 }}
|
onClick={handleClosePreview}
|
||||||
>
|
style={{ flex: 1, borderRadius: 8 }}
|
||||||
关闭
|
>
|
||||||
</Button>
|
关闭
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
|
||||||
|
style={{width:'100%', borderRadius: 8 ,marginTop:20,color:'#4c49cc'}}
|
||||||
|
>
|
||||||
|
|
||||||
|
推送巨量引擎后台
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -332,6 +332,7 @@ const LoginPage: React.FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="password" rules={[{ required: true, message: '请设置密码' }, { min: 6, message: '密码至少6位' }]}>
|
<Form.Item name="password" rules={[{ required: true, message: '请设置密码' }, { min: 6, message: '密码至少6位' }]}>
|
||||||
<Input.Password prefix={<LockOutlined style={{ color: '#94a3b8', marginRight: 8 }} />} placeholder="请设置密码(至少6位)" style={inputStyle} />
|
<Input.Password prefix={<LockOutlined style={{ color: '#94a3b8', marginRight: 8 }} />} placeholder="请设置密码(至少6位)" style={inputStyle} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
Reference in New Issue
Block a user