This commit is contained in:
sjy
2026-07-01 13:44:19 +08:00
parent 823a58d86f
commit 04f34c1075
5 changed files with 210 additions and 146 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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>
+6 -2
View File
@@ -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`,
+44 -20
View File
@@ -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,