ai创建
This commit is contained in:
+94
-94
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-C_zmnmex.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-BsNc32TJ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BhPFzWLH.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -338,11 +338,15 @@ export async function getEngine(): Promise<any> {
|
||||
}
|
||||
},
|
||||
default_size: "2K",
|
||||
priority: 10
|
||||
priority: 10,
|
||||
maxImageCount: 4,
|
||||
maxVideoCount: 0
|
||||
}
|
||||
],
|
||||
video: [
|
||||
{ id: 'mock', name: 'Seedance', provider: 'seedance', supportedRatios: ['16:9', '9:16', '1:1', '4:3', '3:4', '21:9'], supportedResolutions: ['480p', '720p', '1080p'], supportedDurations: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] }
|
||||
{ id: '0019e1697667d0eff39', name: 'Seedance 2.0', provider: 'ark', model_name: 'seedance-2-0', supportedRatios: ['16:9', '9:16', '1:1', '4:3', '3:4', '21:9'], supportedResolutions: ['480p', '720p', '1080p'], supportedDurations: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], maxImageCount: 4, maxVideoCount: 1 },
|
||||
{ id: '0019e1697667d0eff38', name: 'Seedance 2.0 fast', provider: 'ark', model_name: 'seedance-2-0-fast', supportedRatios: ['16:9', '9:16', '1:1'], supportedResolutions: ['720p', '1080p'], supportedDurations: [5, 8, 10], maxImageCount: 2, maxVideoCount: 1 },
|
||||
{ id: '0019f1b89b545d8e4b1', name: 'Seedance 2.0 mini', provider: 'ark', model_name: 'seedance-2-0-mini', supportedRatios: ['16:9', '9:16', '1:1'], supportedResolutions: ['480p', '720p'], supportedDurations: [4, 5, 6], maxImageCount: 1, maxVideoCount: 1 }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -143,16 +143,17 @@ const StorageCard: React.FC<{ data: ResourceCapacityData | null }> = ({ data })
|
||||
color: '#475569',
|
||||
}}
|
||||
>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
{/* <span style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<DatabaseOutlined style={{ fontSize: 12, color: isOver ? '#ef4444' : '#6366f1' }} />
|
||||
{rawPercent.toFixed(1)}%
|
||||
</span>
|
||||
</span> */}
|
||||
{data.enabled ? (
|
||||
<span style={{ fontWeight: 500, color: isOver ? '#ef4444' : '#1e293b' }}>
|
||||
{used.toFixed(2)} / {total.toFixed(2)} {unit}
|
||||
</span>
|
||||
<></>
|
||||
) : (
|
||||
|
||||
<span style={{ fontWeight: 500, color: '#1e293b' }}>
|
||||
<DatabaseOutlined style={{ fontSize: 12, color: isOver ? '#ef4444' : '#6366f1', marginRight: 4 }} />
|
||||
|
||||
当前使用 {usedAuto.val} {usedAuto.unit}
|
||||
</span>
|
||||
)}
|
||||
@@ -162,12 +163,46 @@ const StorageCard: React.FC<{ data: ResourceCapacityData | null }> = ({ data })
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 6,
|
||||
height: 20,
|
||||
background: '#e2e8f0',
|
||||
borderRadius: 3,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: '100%',
|
||||
background: 'transparent',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 6,
|
||||
fontSize: 12,
|
||||
color: '#000000ff',
|
||||
padding: '0 8px',
|
||||
|
||||
|
||||
}}>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<DatabaseOutlined style={{ fontSize: 12, color: '#000000ff' }} />
|
||||
{rawPercent.toFixed(1)}%
|
||||
</span>
|
||||
{data.enabled ? (
|
||||
<span style={{ fontWeight: 500, color: isOver ? '#ef4444' : '#000000ff' }}>
|
||||
{used.toFixed(2)} / {total.toFixed(2)} {unit}
|
||||
</span>
|
||||
) : (
|
||||
<span style={{ fontWeight: 500, color: '#1e293b' }}>
|
||||
当前使用 {usedAuto.val} {usedAuto.unit}
|
||||
</span>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
width: `${barPercent}%`,
|
||||
@@ -179,6 +214,7 @@ const StorageCard: React.FC<{ data: ResourceCapacityData | null }> = ({ data })
|
||||
transition: 'width 0.4s ease',
|
||||
}}
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
@@ -283,9 +319,9 @@ const AppLayout: React.FC = () => {
|
||||
const [contactHovered, setContactHovered] = useState(false);
|
||||
const [contactForm] = Form.useForm();
|
||||
const [submittingContact, setSubmittingContact] = useState(false);
|
||||
const [contactPosition, setContactPosition] = useState<{ x: number; y: number }>(() => ({
|
||||
x: 24,
|
||||
y: window.innerHeight * 0.75
|
||||
const [contactPosition, setContactPosition] = useState<{ x: number; y: number }>(() => ({
|
||||
x: 24,
|
||||
y: window.innerHeight * 0.75
|
||||
}));
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const hasMovedRef = useRef(false);
|
||||
@@ -408,16 +444,16 @@ const AppLayout: React.FC = () => {
|
||||
|
||||
const handleContactMouseMove = (e: MouseEvent) => {
|
||||
if (!isDragging) return;
|
||||
|
||||
|
||||
const deltaX = Math.abs(e.clientX - dragStartRef.current.x);
|
||||
const deltaY = Math.abs(e.clientY - dragStartRef.current.y);
|
||||
|
||||
|
||||
if (deltaX > 5 || deltaY > 5) {
|
||||
hasMovedRef.current = true;
|
||||
}
|
||||
|
||||
|
||||
const newY = Math.max(60, Math.min(window.innerHeight - 60, e.clientY - (dragStartRef.current.y - contactPosition.y)));
|
||||
|
||||
|
||||
setContactPosition(prev => ({ x: prev.x, y: newY }));
|
||||
};
|
||||
|
||||
@@ -425,7 +461,7 @@ const AppLayout: React.FC = () => {
|
||||
const moved = hasMovedRef.current;
|
||||
setIsDragging(false);
|
||||
hasMovedRef.current = false;
|
||||
|
||||
|
||||
if (!moved) {
|
||||
setContactModalOpen(true);
|
||||
}
|
||||
@@ -680,12 +716,12 @@ const AppLayout: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div key={item.id}>
|
||||
<div
|
||||
<div
|
||||
onClick={() => {
|
||||
if (!(isGroup || hasChildren) && item.path) {
|
||||
navigate(item.path);
|
||||
}
|
||||
}}
|
||||
}}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
@@ -720,7 +756,7 @@ const AppLayout: React.FC = () => {
|
||||
{item.label}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
{(isGroup || hasChildren) && (
|
||||
<div style={{ overflow: 'hidden' }}>
|
||||
{(childMap[item.id] || []).map(c => renderMenuItem(c, depth + 1))}
|
||||
@@ -760,7 +796,7 @@ const AppLayout: React.FC = () => {
|
||||
boxShadow: '0 4px 16px rgba(99, 102, 241, 0.35)',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
|
||||
|
||||
>
|
||||
{siteLogo ? (
|
||||
<img src={siteLogo} alt="logo" style={{ width: 28, height: 28, objectFit: 'contain' }} />
|
||||
@@ -811,9 +847,9 @@ const AppLayout: React.FC = () => {
|
||||
<span>充值积分</span>
|
||||
</div>
|
||||
{/* 资源存储容量展示(来自 getUser.resourceCapacity) */}
|
||||
<StorageCard data={resourceCapacity} />
|
||||
<StorageCard data={resourceCapacity} />
|
||||
|
||||
<div style={{ padding: '16px 16px', flexShrink: 0 ,paddingTop: 0}}>
|
||||
<div style={{ padding: '16px 16px', flexShrink: 0, paddingTop: 0 }}>
|
||||
<Dropdown menu={{ items: userMenuItems, onClick: handleUserMenuClick }} placement="topRight" arrow>
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center',
|
||||
@@ -840,7 +876,7 @@ const AppLayout: React.FC = () => {
|
||||
flexShrink: 0,
|
||||
boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)',
|
||||
}} />
|
||||
|
||||
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ color: '#1e293b', fontSize: 16, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', letterSpacing: -0.01 }}>
|
||||
{user?.username}
|
||||
@@ -859,7 +895,7 @@ const AppLayout: React.FC = () => {
|
||||
</div>
|
||||
</Dropdown>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -975,7 +1011,7 @@ const AppLayout: React.FC = () => {
|
||||
{menuIcon}
|
||||
</span>
|
||||
)}
|
||||
<span className="mobile-menu-label" style={{
|
||||
<span className="mobile-menu-label" style={{
|
||||
paddingLeft: menuType === 'group' ? 0 : 0,
|
||||
color: menuType === 'group' ? '#94a3b8' : (isActive ? '#4f46e5' : '#475569'),
|
||||
fontWeight: menuType === 'group' ? 600 : (isActive ? 600 : 400),
|
||||
@@ -1010,7 +1046,7 @@ const AppLayout: React.FC = () => {
|
||||
<span className="mobile-menu-icon" style={{ color: childActive ? '#6366f1' : '#94a3b8' }}>
|
||||
{childIcon}
|
||||
</span>
|
||||
<span className="mobile-menu-label" style={{
|
||||
<span className="mobile-menu-label" style={{
|
||||
color: childActive ? '#4f46e5' : '#64748b',
|
||||
fontWeight: childActive ? 600 : 400,
|
||||
fontSize: 14,
|
||||
@@ -1099,7 +1135,7 @@ const AppLayout: React.FC = () => {
|
||||
</span>
|
||||
<span className="mobile-menu-label" style={{ color: '#fff', fontWeight: 600 }}>充值积分</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div
|
||||
className="mobile-menu-item"
|
||||
onClick={() => {
|
||||
@@ -1162,9 +1198,9 @@ const AppLayout: React.FC = () => {
|
||||
<InfoOutlined style={{ color: '#6366f1', fontSize: 14 }} />
|
||||
<Typography.Text style={{ color: '#64748b', fontSize: 13 }}>
|
||||
当前平台仅支持支付宝/微信扫码充值,如需转账支付请
|
||||
<Typography.Text
|
||||
style={{
|
||||
color: '#ff0000ff',
|
||||
<Typography.Text
|
||||
style={{
|
||||
color: '#ff0000ff',
|
||||
cursor: 'pointer',
|
||||
textDecoration: 'underline',
|
||||
}}
|
||||
@@ -1444,7 +1480,7 @@ const AppLayout: React.FC = () => {
|
||||
|
||||
<NotificationPopup />
|
||||
|
||||
<div
|
||||
<div
|
||||
className="contact-button-wrapper"
|
||||
style={{
|
||||
right: `${contactPosition.x}px`,
|
||||
|
||||
@@ -143,6 +143,12 @@ const AIChatPage: React.FC = () => {
|
||||
setCurrentMedia,
|
||||
} = useAppStore();
|
||||
|
||||
// 获取当前选中引擎的媒体上传限制
|
||||
const currentEngineList = mediaType === 'image' ? enginesele?.image : enginesele?.video;
|
||||
const currentEngine = currentEngineList?.find((e: any) => e.id === countType);
|
||||
const maxImageCount = currentEngine?.maxImageCount ?? 4;
|
||||
const maxVideoCount = currentEngine?.maxVideoCount ?? 1;
|
||||
|
||||
const [uploading, setUploading] = useState<boolean>(false);
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
@@ -427,19 +433,19 @@ const AIChatPage: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
let supportedSizes = (data as any).engine.image?.[0]?.supportedSizes || {};
|
||||
let supportedResolutions = [];
|
||||
let twokwidth = [];
|
||||
let fourkwidth = [];
|
||||
let supportedSizes = (data as any).engine?.image?.[0]?.supported_sizes || {};
|
||||
let supportedResolutions: string[] = [];
|
||||
let twokwidth: string[] = [];
|
||||
let fourkwidth: string[] = [];
|
||||
|
||||
for (let key in supportedSizes["2K"]) {
|
||||
supportedResolutions.push(key);
|
||||
twokwidth.push(supportedSizes["2K"][key]);
|
||||
}
|
||||
|
||||
const newRatioOptions = supportedResolutions.map((res: any, index: number) => ({
|
||||
const newRatioOptions = supportedResolutions.map((res: any) => ({
|
||||
value: res,
|
||||
label: String(index),
|
||||
label: res,
|
||||
}));
|
||||
setRatioOptions(newRatioOptions);
|
||||
|
||||
@@ -838,6 +844,18 @@ const AIChatPage: React.FC = () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取当前选中引擎的限制
|
||||
const currentEngineList = mediaType === 'image' ? enginesele.image : enginesele.video;
|
||||
const currentEngine = currentEngineList?.find((e: any) => e.id === countType);
|
||||
const maxImage = currentEngine?.maxImageCount ?? 4;
|
||||
const maxVideo = currentEngine?.maxVideoCount ?? 1;
|
||||
|
||||
// 根据 mediaType 判断是否允许该文件类型
|
||||
if (mediaType === 'image' && isVideo) {
|
||||
message.error('图片模式仅支持上传图片');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证文件大小
|
||||
const maxMB = isVideo ? 100 : 10;
|
||||
if (file.size / 1024 / 1024 > maxMB) {
|
||||
@@ -845,17 +863,17 @@ const AIChatPage: React.FC = () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证图片数量(最多4张)
|
||||
// 验证图片数量
|
||||
const imageCount = currentMedia.filter((m) => m.type === 'image').length;
|
||||
if (isImage && imageCount >= 4) {
|
||||
message.error('最多上传4张图片');
|
||||
if (isImage && imageCount >= maxImage) {
|
||||
message.error(`该引擎最多上传${maxImage}张图片`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证视频数量(最多1个)
|
||||
// 验证视频数量
|
||||
const videoCount = currentMedia.filter((m) => m.type === 'video').length;
|
||||
if (isVideo && videoCount >= 1) {
|
||||
message.error('最多上传1个视频');
|
||||
if (isVideo && videoCount >= maxVideo) {
|
||||
message.error(`该引擎最多上传${maxVideo}个视频`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1092,9 +1110,9 @@ const AIChatPage: React.FC = () => {
|
||||
border: '1px solid rgba(99, 102, 241, 0.08)',
|
||||
position: 'relative', overflow: 'hidden', flexWrap: 'wrap', gap: 12,
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} />
|
||||
<div>
|
||||
<div style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||
<div style={{ width: '15%', height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} />
|
||||
<div style={{ flex: 1, minWidth: 0, textAlign: 'center' }}>
|
||||
<h2 style={{
|
||||
margin: 0,
|
||||
fontSize: 18,
|
||||
@@ -1102,7 +1120,7 @@ const AIChatPage: React.FC = () => {
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
backgroundClip: 'text'
|
||||
backgroundClip: 'text',
|
||||
}}>
|
||||
AI创作
|
||||
</h2>
|
||||
@@ -1110,7 +1128,7 @@ const AIChatPage: React.FC = () => {
|
||||
输入想法、剧本或上传参考,智能生成视频/图片
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
|
||||
<div style={{ width: '15%', height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1633,11 +1651,14 @@ const AIChatPage: React.FC = () => {
|
||||
}}>
|
||||
{/* 上传按钮 */}
|
||||
<Upload
|
||||
accept="image/*,video/*"
|
||||
accept={mediaType === 'image' ? 'image/*' : 'image/*,video/*'}
|
||||
showUploadList={false}
|
||||
beforeUpload={handleUpload}
|
||||
>
|
||||
<Tooltip title={`图片${currentMedia.filter(m => m.type === 'image').length}/4,视频${currentMedia.filter(m => m.type === 'video').length}/1`}>
|
||||
<Tooltip title={mediaType === 'image'
|
||||
? `图片${currentMedia.filter(m => m.type === 'image').length}/${maxImageCount}`
|
||||
: `图片${currentMedia.filter(m => m.type === 'image').length}/${maxImageCount},视频${currentMedia.filter(m => m.type === 'video').length}/${maxVideoCount}`
|
||||
}>
|
||||
<div
|
||||
style={{
|
||||
width: 48,
|
||||
@@ -1679,7 +1700,10 @@ const AIChatPage: React.FC = () => {
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
onKeyPress={handleKeyPress}
|
||||
placeholder="上传最多4张参考图,输入提示词描述您想生成的画面..."
|
||||
placeholder={mediaType === 'image'
|
||||
? `上传最多${maxImageCount}张参考图,输入提示词描述您想生成的画面...`
|
||||
: `上传参考图(最多${maxImageCount}张)和视频(最多${maxVideoCount}个),输入提示词描述您想生成的画面...`
|
||||
}
|
||||
autoSize={{ minRows: 1, maxRows: 4 }}
|
||||
style={{
|
||||
flex: 1,
|
||||
|
||||
Reference in New Issue
Block a user