1
This commit is contained in:
+1
-1
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-7yqsyI.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-Cxnc8Q5N.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Bi8mcSs8.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1111,8 +1111,8 @@ const AIChatPage: React.FC = () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 图片尺寸校验
|
||||
if (isImage && mediaType !== 'video') {
|
||||
// 图片尺寸校验(所有图片都需要校验)
|
||||
if (isImage) {
|
||||
try {
|
||||
const { width, height } = await getImageDimensions(file);
|
||||
const error = validateImageDimensions(width, height);
|
||||
@@ -2078,6 +2078,200 @@ const AIChatPage: React.FC = () => {
|
||||
|
||||
|
||||
{/* 输入区域 - 始终显示 */}
|
||||
<div style={{ position: 'relative' }}>
|
||||
{/* 层叠附件展示 - 位于输入区域左侧,不影响高度 */}
|
||||
{currentMedia.length > 0 && !(mediaType === 'video' && referenceMode === 'first_last_frame') && (
|
||||
<div
|
||||
className="ai-reference-stack-float"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: '100%',
|
||||
bottom: 0,
|
||||
marginRight: 12,
|
||||
zIndex: 50,
|
||||
}}
|
||||
onMouseEnter={openMediaStackTray}
|
||||
onMouseLeave={closeMediaStackTray}
|
||||
>
|
||||
<div style={{ position: 'relative', width: 104, height: 66 }}>
|
||||
{currentMedia.slice(0, 4).map((media, idx) => {
|
||||
const reversedIdx = currentMedia.slice(0, 4).length - 1 - idx;
|
||||
const offsetX = mediaStackHovered ? idx * 7 : idx * 3;
|
||||
const offsetY = mediaStackHovered ? idx * 5 : idx * 2;
|
||||
return (
|
||||
<div
|
||||
key={`media-float-${idx}`}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: offsetX,
|
||||
top: offsetY,
|
||||
zIndex: idx + 1,
|
||||
transition: 'all 0.28s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
transform: `scale(${1 - reversedIdx * 0.035})`,
|
||||
opacity: mediaStackHovered ? 1 : (1 - reversedIdx * 0.15),
|
||||
}}
|
||||
>
|
||||
<div style={{ position: 'relative', width: 50, height: 54 }}>
|
||||
{media.type === 'image' ? (
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${media.url}`}
|
||||
alt={media.name}
|
||||
onClick={() => {
|
||||
setAttachmentPreviewUrl(media.url);
|
||||
setAttachmentPreviewType('image');
|
||||
setAttachmentPreviewName(media.name);
|
||||
setAttachmentPreviewVisible(true);
|
||||
}}
|
||||
style={{ width: 50, height: 54, objectFit: 'cover', borderRadius: 11, cursor: 'pointer', border: '2px solid rgba(255,255,255,0.98)', boxShadow: '0 8px 18px rgba(31, 41, 55, 0.14)' }}
|
||||
/>
|
||||
) : (
|
||||
<video
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${media.url}`}
|
||||
muted
|
||||
onClick={() => {
|
||||
setAttachmentPreviewUrl(media.url);
|
||||
setAttachmentPreviewType('video');
|
||||
setAttachmentPreviewName(media.name);
|
||||
setAttachmentPreviewVisible(true);
|
||||
}}
|
||||
style={{ width: 50, height: 54, objectFit: 'cover', borderRadius: 11, cursor: 'pointer', border: '2px solid rgba(255,255,255,0.98)', boxShadow: '0 8px 18px rgba(31, 41, 55, 0.14)' }}
|
||||
/>
|
||||
)}
|
||||
{mediaStackHovered && (
|
||||
<button
|
||||
onClick={() => handleRemoveMedia(idx)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: -5,
|
||||
right: -5,
|
||||
width: 18,
|
||||
height: 18,
|
||||
border: '1px solid rgba(255,255,255,0.9)',
|
||||
background: '#A45B5B',
|
||||
borderRadius: 50,
|
||||
cursor: 'pointer',
|
||||
color: '#fff',
|
||||
fontSize: 9,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 4px 10px rgba(164, 91, 91, 0.28)',
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined style={{ fontSize: 9 }} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{currentMedia.length > 4 && (
|
||||
<div style={{ position: 'absolute', right: -8, bottom: -4, zIndex: 10, background: '#8b5cf6', color: '#fff', fontSize: 10, fontWeight: 700, padding: '2px 7px', borderRadius: 999, boxShadow: '0 6px 14px rgba(31, 41, 55, 0.18)' }}>
|
||||
+{currentMedia.length - 4}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{mediaStackHovered && currentMedia.length > 0 && (
|
||||
<div
|
||||
className="ai-reference-hover-bridge"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: -14,
|
||||
bottom: 46,
|
||||
width: 392,
|
||||
height: 44,
|
||||
background: 'transparent',
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{mediaStackHovered && currentMedia.length > 0 && (
|
||||
<div
|
||||
className="ai-reference-expanded-tray"
|
||||
onMouseEnter={openMediaStackTray}
|
||||
onMouseLeave={closeMediaStackTray}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
bottom: 54,
|
||||
width: 360,
|
||||
maxWidth: 'min(360px, calc(100vw - 360px))',
|
||||
padding: '10px 10px 8px',
|
||||
borderRadius: 18,
|
||||
background: 'rgba(255,255,255,0.98)',
|
||||
border: '1px solid #E7EAF0',
|
||||
boxShadow: '0 24px 60px rgba(31, 41, 55, 0.12)',
|
||||
backdropFilter: 'blur(18px)',
|
||||
zIndex: 20,
|
||||
pointerEvents: 'auto',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
|
||||
<span style={{ fontSize: 12, color: '#344054', fontWeight: 800 }}>已上传参考素材</span>
|
||||
<span style={{ fontSize: 11, color: '#98A2B3', fontWeight: 700 }}>{currentMedia.length}/{maxImageCount + maxVideoCount}</span>
|
||||
</div>
|
||||
<div className="ai-reference-tray-scroll" style={{ display: 'flex', gap: 8, overflowX: 'auto', paddingBottom: 2 }}>
|
||||
{currentMedia.map((media, idx) => (
|
||||
<div key={`tray-float-${idx}`} style={{ position: 'relative', flex: '0 0 auto', width: 58 }}>
|
||||
{media.type === 'image' ? (
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${media.url}`}
|
||||
alt={media.name}
|
||||
onClick={() => {
|
||||
setAttachmentPreviewUrl(media.url);
|
||||
setAttachmentPreviewType('image');
|
||||
setAttachmentPreviewName(media.name);
|
||||
setAttachmentPreviewVisible(true);
|
||||
}}
|
||||
style={{ width: 58, height: 62, objectFit: 'cover', borderRadius: 12, cursor: 'pointer', border: '1px solid #E7EAF0', boxShadow: '0 6px 14px rgba(31, 41, 55, 0.08)' }}
|
||||
/>
|
||||
) : (
|
||||
<video
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${media.url}`}
|
||||
muted
|
||||
onClick={() => {
|
||||
setAttachmentPreviewUrl(media.url);
|
||||
setAttachmentPreviewType('video');
|
||||
setAttachmentPreviewName(media.name);
|
||||
setAttachmentPreviewVisible(true);
|
||||
}}
|
||||
style={{ width: 58, height: 62, objectFit: 'cover', borderRadius: 12, cursor: 'pointer', border: '1px solid #E7EAF0', boxShadow: '0 6px 14px rgba(31, 41, 55, 0.08)' }}
|
||||
/>
|
||||
)}
|
||||
<span style={{ display: 'block', marginTop: 4, textAlign: 'center', fontSize: 10, color: '#667085', fontWeight: 700, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{media.label || `${media.type === 'image' ? '图片' : '视频'}${idx + 1}`}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => handleRemoveMedia(idx)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: -6,
|
||||
right: -6,
|
||||
width: 20,
|
||||
height: 20,
|
||||
border: '1.5px solid #fff',
|
||||
background: '#A45B5B',
|
||||
borderRadius: 50,
|
||||
cursor: 'pointer',
|
||||
color: '#fff',
|
||||
fontSize: 10,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 4px 10px rgba(164, 91, 91, 0.3)',
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined style={{ fontSize: 10 }} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
background: '#ffffff',
|
||||
@@ -2196,175 +2390,6 @@ const AIChatPage: React.FC = () => {
|
||||
</div>
|
||||
</div>) : (
|
||||
<>
|
||||
{currentMedia.length > 0 ? (
|
||||
<div
|
||||
className="ai-reference-stack"
|
||||
style={{ position: 'relative', width: '100%', minHeight: 66, display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 40 }}
|
||||
onMouseEnter={openMediaStackTray}
|
||||
onMouseLeave={closeMediaStackTray}
|
||||
>
|
||||
<div style={{ position: 'relative', width: 104, height: 66 }}>
|
||||
{currentMedia.slice(0, 4).map((media, idx) => {
|
||||
const reversedIdx = currentMedia.slice(0, 4).length - 1 - idx;
|
||||
const offsetX = mediaStackHovered ? idx * 7 : idx * 3;
|
||||
const offsetY = mediaStackHovered ? idx * 5 : idx * 2;
|
||||
return (
|
||||
<div
|
||||
key={`media-${idx}`}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: offsetX,
|
||||
top: offsetY,
|
||||
zIndex: idx + 1,
|
||||
transition: 'all 0.28s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
transform: `scale(${1 - reversedIdx * 0.035})`,
|
||||
opacity: mediaStackHovered ? 1 : (1 - reversedIdx * 0.15),
|
||||
}}
|
||||
>
|
||||
<div style={{ position: 'relative', width: 50, height: 54 }}>
|
||||
{media.type === 'image' ? (
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${media.url}`}
|
||||
alt={media.name}
|
||||
onClick={() => {
|
||||
setAttachmentPreviewUrl(media.url);
|
||||
setAttachmentPreviewType('image');
|
||||
setAttachmentPreviewName(media.name);
|
||||
setAttachmentPreviewVisible(true);
|
||||
}}
|
||||
style={{ width: 50, height: 54, objectFit: 'cover', borderRadius: 11, cursor: 'pointer', border: '2px solid rgba(255,255,255,0.98)', boxShadow: '0 8px 18px rgba(31, 41, 55, 0.14)' }}
|
||||
/>
|
||||
) : (
|
||||
<video
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${media.url}`}
|
||||
muted
|
||||
onClick={() => {
|
||||
setAttachmentPreviewUrl(media.url);
|
||||
setAttachmentPreviewType('video');
|
||||
setAttachmentPreviewName(media.name);
|
||||
setAttachmentPreviewVisible(true);
|
||||
}}
|
||||
style={{ width: 50, height: 54, objectFit: 'cover', borderRadius: 11, cursor: 'pointer', border: '2px solid rgba(255,255,255,0.98)', boxShadow: '0 8px 18px rgba(31, 41, 55, 0.14)' }}
|
||||
/>
|
||||
)}
|
||||
{mediaStackHovered && (
|
||||
<button
|
||||
onClick={() => handleRemoveMedia(idx)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: -5,
|
||||
right: -5,
|
||||
width: 18,
|
||||
height: 18,
|
||||
border: '1px solid rgba(255,255,255,0.9)',
|
||||
background: '#A45B5B',
|
||||
borderRadius: 50,
|
||||
cursor: 'pointer',
|
||||
color: '#fff',
|
||||
fontSize: 9,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 4px 10px rgba(164, 91, 91, 0.28)',
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined style={{ fontSize: 9 }} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{currentMedia.length > 4 && (
|
||||
<div style={{ position: 'absolute', right: -8, bottom: -4, zIndex: 10, background: '#8b5cf6', color: '#fff', fontSize: 10, fontWeight: 700, padding: '2px 7px', borderRadius: 999, boxShadow: '0 6px 14px rgba(31, 41, 55, 0.18)' }}>
|
||||
+{currentMedia.length - 4}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{mediaStackHovered && currentMedia.length > 0 && (
|
||||
<div
|
||||
className="ai-reference-hover-bridge"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: -14,
|
||||
bottom: 46,
|
||||
width: 392,
|
||||
height: 44,
|
||||
background: 'transparent',
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{mediaStackHovered && currentMedia.length > 0 && (
|
||||
<div
|
||||
className="ai-reference-expanded-tray"
|
||||
onMouseEnter={openMediaStackTray}
|
||||
onMouseLeave={closeMediaStackTray}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
bottom: 54,
|
||||
width: 360,
|
||||
maxWidth: 'min(360px, calc(100vw - 360px))',
|
||||
padding: '10px 10px 8px',
|
||||
borderRadius: 18,
|
||||
background: 'rgba(255,255,255,0.98)',
|
||||
border: '1px solid #E7EAF0',
|
||||
boxShadow: '0 24px 60px rgba(31, 41, 55, 0.12)',
|
||||
backdropFilter: 'blur(18px)',
|
||||
zIndex: 20,
|
||||
pointerEvents: 'auto',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
|
||||
<span style={{ fontSize: 12, color: '#344054', fontWeight: 800 }}>已上传参考素材</span>
|
||||
<span style={{ fontSize: 11, color: '#98A2B3', fontWeight: 700 }}>{currentMedia.length}/{maxImageCount + maxVideoCount}</span>
|
||||
</div>
|
||||
<div className="ai-reference-tray-scroll" style={{ display: 'flex', gap: 8, overflowX: 'auto', paddingBottom: 2 }}>
|
||||
{currentMedia.map((media, idx) => (
|
||||
<div key={`tray-${idx}`} style={{ position: 'relative', flex: '0 0 auto', width: 58 }}>
|
||||
{media.type === 'image' ? (
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${media.url}`}
|
||||
alt={media.name}
|
||||
onClick={() => {
|
||||
setAttachmentPreviewUrl(media.url);
|
||||
setAttachmentPreviewType('image');
|
||||
setAttachmentPreviewName(media.name);
|
||||
setAttachmentPreviewVisible(true);
|
||||
}}
|
||||
style={{ width: 58, height: 62, objectFit: 'cover', borderRadius: 12, cursor: 'pointer', border: '1px solid #E7EAF0', boxShadow: '0 6px 14px rgba(31, 41, 55, 0.08)' }}
|
||||
/>
|
||||
) : (
|
||||
<video
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${media.url}`}
|
||||
muted
|
||||
onClick={() => {
|
||||
setAttachmentPreviewUrl(media.url);
|
||||
setAttachmentPreviewType('video');
|
||||
setAttachmentPreviewName(media.name);
|
||||
setAttachmentPreviewVisible(true);
|
||||
}}
|
||||
style={{ width: 58, height: 62, objectFit: 'cover', borderRadius: 12, cursor: 'pointer', border: '1px solid #E7EAF0', boxShadow: '0 6px 14px rgba(31, 41, 55, 0.08)' }}
|
||||
/>
|
||||
)}
|
||||
<span style={{ display: 'block', marginTop: 4, textAlign: 'center', fontSize: 10, color: '#667085', fontWeight: 700, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{media.label || `${media.type === 'image' ? '图片' : '视频'}${idx + 1}`}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => handleRemoveMedia(idx)}
|
||||
style={{ position: 'absolute', top: 0, right: -6, width: 18, height: 18, border: '1px solid rgba(255,255,255,0.9)', background: '#A45B5B', borderRadius: 50, cursor: 'pointer', color: '#fff', fontSize: 9, display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 4px 10px rgba(164, 91, 91, 0.28)', zIndex: 10 }}
|
||||
>
|
||||
<DeleteOutlined style={{ fontSize: 9 }} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* 上传按钮 - 非首尾帧模式且还有上传空间时显示 */}
|
||||
{!(mediaType === 'video' && referenceMode === 'first_last_frame') && (
|
||||
@@ -3464,6 +3489,7 @@ const AIChatPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>{/* 结束 position: relative 包装器 */}
|
||||
</Content>
|
||||
</Layout>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user