AI创作模块优化样式

This commit is contained in:
2026-07-02 13:27:59 +08:00
parent 854a251d22
commit 6b7604dd38
+156 -126
View File
@@ -1569,7 +1569,7 @@ const AIChatPage: React.FC = () => {
lineHeight: 1.6,
cursor: 'pointer',
transition: 'all 0.2s ease',
boxShadow: '0 1px 3px rgba(99, 102, 241, 0.04)',
boxShadow: '0 1px 2px rgba(15, 23, 42, 0.03)',
}}
onMouseEnter={() => {
setExpandedPrompts(prev => {
@@ -1726,7 +1726,7 @@ const AIChatPage: React.FC = () => {
borderRadius: 12,
boxShadow: '0 4px 20px rgba(0,0,0,0.15)',
padding: 16,
minWidth: 200,
minWidth: 174,
zIndex: 9999,
}}
onClick={(e) => e.stopPropagation()}
@@ -1805,19 +1805,18 @@ const AIChatPage: React.FC = () => {
{/* 输入区域 - 始终显示 */}
<div
style={{
background: 'rgba(255,255,255,0.15)',
backdropFilter: 'blur(24px)',
borderRadius: 24,
padding: 16,
boxShadow: '0 12px 40px rgba(99, 102, 241, 0.12), 0 4px 16px rgba(0,0,0,0.06), inset 0 1px 0 rgba(255,255,255,0.6)',
transition: 'all 0.3s ease',
border: '1px solid rgba(99, 102, 241, 0.12)',
background: '#ffffff',
borderRadius: 22,
padding: '16px 18px',
boxShadow: '0 10px 28px rgba(15, 23, 42, 0.06)',
transition: 'all 0.25s ease',
border: '1px solid #eef1f5',
}}
>
{/* 左右布局容器 */}
<div style={{ display: 'flex', gap: 16, marginBottom: 14 }}>
{/* 左侧附件区域 - 20% */}
<div style={{ width: '20%', minWidth: 100, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'flex-start' }}>
{/* 上方输入布局 */}
<div style={{ display: 'flex', gap: 16, alignItems: 'flex-start', marginBottom: 14 }}>
{/* 左侧附件区域 */}
<div style={{ width: 68, minWidth: 68, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'flex-start', paddingTop: 2 }}>
{/* 首尾帧模式 */}
{mediaType === 'video' && referenceMode === 'first_last_frame' ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6, width: '100%' }}>
@@ -2010,11 +2009,11 @@ const AIChatPage: React.FC = () => {
<>
{currentMedia.length > 0 ? (
<div
style={{ position: 'relative', width: '100%', minHeight: 80, display: 'flex', justifyContent: 'center' }}
style={{ position: 'relative', width: '100%', minHeight: 62, display: 'flex', justifyContent: 'center' }}
onMouseEnter={() => setMediaStackHovered(true)}
onMouseLeave={() => setMediaStackHovered(false)}
>
<div style={{ position: 'relative', width: 70, height: 70 }}>
<div style={{ position: 'relative', width: 54, height: 62 }}>
{currentMedia.slice(0, 4).map((media, idx) => {
const reversedIdx = currentMedia.slice(0, 4).length - 1 - idx;
const offsetX = mediaStackHovered ? idx * 6 : idx * 3;
@@ -2032,7 +2031,7 @@ const AIChatPage: React.FC = () => {
opacity: mediaStackHovered ? 1 : (1 - reversedIdx * 0.18),
}}
>
<div style={{ position: 'relative', width: 60, height: 60 }}>
<div style={{ position: 'relative', width: 50, height: 58 }}>
{media.type === 'image' ? (
<img
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${media.url}`}
@@ -2043,7 +2042,7 @@ const AIChatPage: React.FC = () => {
setAttachmentPreviewName(media.name);
setAttachmentPreviewVisible(true);
}}
style={{ width: 60, height: 60, objectFit: 'cover', borderRadius: 8, cursor: 'pointer', border: '2px solid rgba(255,255,255,0.9)', boxShadow: '0 2px 8px rgba(99, 102, 241, 0.2)' }}
style={{ width: 50, height: 58, objectFit: 'cover', borderRadius: 8, cursor: 'pointer', border: '2px solid rgba(255,255,255,0.95)', boxShadow: '0 2px 8px rgba(15, 23, 42, 0.12)' }}
/>
) : (
<video
@@ -2055,7 +2054,7 @@ const AIChatPage: React.FC = () => {
setAttachmentPreviewName(media.name);
setAttachmentPreviewVisible(true);
}}
style={{ width: 60, height: 60, objectFit: 'cover', borderRadius: 8, cursor: 'pointer', border: '2px solid rgba(255,255,255,0.9)', boxShadow: '0 2px 8px rgba(99, 102, 241, 0.2)' }}
style={{ width: 50, height: 58, objectFit: 'cover', borderRadius: 8, cursor: 'pointer', border: '2px solid rgba(255,255,255,0.95)', boxShadow: '0 2px 8px rgba(15, 23, 42, 0.12)' }}
/>
)}
<button
@@ -2096,7 +2095,7 @@ const AIChatPage: React.FC = () => {
{/* 上传按钮 - 非首尾帧模式且还有上传空间时显示 */}
{!(mediaType === 'video' && referenceMode === 'first_last_frame') && (
<div style={{ marginTop: currentMedia.length > 0 ? 8 : 0, width: '100%', maxWidth: 70, display: 'flex', justifyContent: 'center' }}>
<div style={{ marginTop: currentMedia.length > 0 ? 6 : 0, width: '100%', maxWidth: 58, display: 'flex', justifyContent: 'center' }}>
<Upload
accept={mediaType === 'image' ? 'image/*' : 'image/*,video/*'}
showUploadList={false}
@@ -2108,30 +2107,39 @@ const AIChatPage: React.FC = () => {
}>
<div
style={{
width: 70,
aspectRatio: '1',
borderRadius: 10,
border: '2px dashed rgba(99, 102, 241, 0.3)',
width: 48,
height: 68,
borderRadius: 6,
border: '1px solid #eef1f5',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
transition: 'all 0.25s ease',
backgroundColor: 'rgba(99, 102, 241, 0.04)',
backgroundColor: '#f3f6f8',
flexDirection: 'column',
gap: 6,
transform: currentMedia.length > 0 ? 'none' : 'rotate(-7deg)',
boxShadow: '0 8px 18px rgba(15, 23, 42, 0.06)',
}}
onMouseEnter={(e) => {
e.currentTarget.style.borderColor = '#6366f1';
e.currentTarget.style.backgroundColor = 'rgba(99, 102, 241, 0.08)';
e.currentTarget.style.borderColor = '#d7e7f2';
e.currentTarget.style.backgroundColor = '#eef7fb';
e.currentTarget.style.transform = 'rotate(0deg) translateY(-1px)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = 'rgba(99, 102, 241, 0.3)';
e.currentTarget.style.backgroundColor = 'rgba(99, 102, 241, 0.04)';
e.currentTarget.style.borderColor = '#eef1f5';
e.currentTarget.style.backgroundColor = '#f3f6f8';
e.currentTarget.style.transform = currentMedia.length > 0 ? 'none' : 'rotate(-7deg)';
}}
>
{uploading ? (
<LoadingOutlined style={{ fontSize: 18, color: '#6366f1' }} />
<LoadingOutlined style={{ fontSize: 17, color: '#94a3b8' }} />
) : (
<PlusOutlined style={{ fontSize: 18, color: '#6366f1' }} />
<>
<PlusOutlined style={{ fontSize: 17, color: '#94a3b8' }} />
<span style={{ fontSize: 10, color: '#94a3b8', fontWeight: 600, lineHeight: 1.1, writingMode: 'vertical-rl', letterSpacing: 1 }}></span>
</>
)}
</div>
</Tooltip>
@@ -2142,21 +2150,21 @@ const AIChatPage: React.FC = () => {
)}
</div>
{/* 右侧输入区域 - 80% */}
<div style={{ width: '80%', flex: 1 }}>
{/* 右侧输入区域 */}
<div style={{ flex: 1, minWidth: 0 }}>
{/* 输入框区域 */}
<div style={{
display: 'flex',
flexDirection: 'column',
gap: 10,
padding: '16px 18px',
backgroundColor: 'rgba(248,250,252,0.7)',
borderRadius: 18,
border: '1px solid rgba(99, 102, 241, 0.1)',
boxShadow: '0 2px 12px rgba(99, 102, 241, 0.06)',
padding: '2px 0 0',
backgroundColor: 'transparent',
borderRadius: 0,
border: 'none',
boxShadow: 'none',
transition: 'all 0.2s ease',
position: 'relative',
minHeight: 120,
minHeight: 82,
}}>
{/* 文本输入框 */}
<TextArea
@@ -2166,25 +2174,26 @@ const AIChatPage: React.FC = () => {
onKeyDown={handleInputKeyDown}
onKeyPress={handleKeyPress}
placeholder={mediaType === 'image'
? `上传参考图,输入提示词描述您想生成的画面...`
? '上传最多12个参考素材,输入文字或 @ 参考内容,自由组合图、文、音、视频多元素,定义精彩互动。例如:@图片1 参考风格生成海报。'
: referenceMode === 'first_last_frame'
? '上传首帧和尾帧,输入提示词描述您想生成的视频...'
: `上传参考图和视频,输入提示词描述您想生成的画面...`
? '上传首帧和尾帧,输入文字描述镜头运动、主体动作和画面氛围。'
: '上传最多12个参考素材,输入文字或 @ 参考内容,自由组合图、文、音、视频多元素,定义精彩互动。例如:@图片1 模仿 @视频1 的动作,音色参考 @音频1。'
}
autoSize={{ minRows: 3, maxRows: 6 }}
style={{
flex: 1,
width: '100%',
borderRadius: 10,
borderRadius: 0,
border: 'none',
outline: 'none',
boxShadow: 'none',
fontSize: 15,
lineHeight: 1.6,
fontSize: 14,
lineHeight: 1.55,
color: '#1e293b',
fontWeight: 400,
resize: 'none',
backgroundColor: 'transparent',
padding: 0,
}}
disabled={loading}
/>
@@ -2242,72 +2251,34 @@ const AIChatPage: React.FC = () => {
))}
</div>
)}
{/* 发送按钮 - 右下角 */}
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
type="primary"
shape="circle"
icon={<ArrowUpOutlined />}
onClick={handleSend}
disabled={
mediaType === 'video' && referenceMode === 'first_last_frame'
? !inputValue.trim() && !firstFrame
: !inputValue.trim() && currentMedia.length === 0
}
loading={loading}
style={{
flexShrink: 0,
width: 40,
height: 40,
borderRadius: 12,
background: (
mediaType === 'video' && referenceMode === 'first_last_frame'
? (inputValue.trim() || firstFrame)
: (inputValue.trim() || currentMedia.length > 0)
)
? 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)'
: '#c7cfdaff',
boxShadow: (
mediaType === 'video' && referenceMode === 'first_last_frame'
? (inputValue.trim() || firstFrame)
: (inputValue.trim() || currentMedia.length > 0)
)
? '0 4px 16px rgba(99, 102, 241, 0.4)'
: 'none',
transition: 'all 0.2s ease',
border: 'none',
}}
/>
</div>
</div>
</div>
</div>
{/* 底部选择器 - 媒体类型和数量 */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 14, paddingTop: 14, borderTop: '1px solid rgba(99, 102, 241, 0.1)' }}>
<Space size={10}>
{/* 底部工具栏 */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, marginTop: 8, paddingTop: 10, borderTop: '1px solid #f1f5f9' }}>
<Space size={8} wrap style={{ flex: 1, minWidth: 0 }}>
{/* 文件类型选择器 */}
<Select
value={mediaType}
onChange={(val) => setMediaType(val)}
style={{
width: 100,
height: 36,
width: 118,
height: 32,
outline: 'none',
border: 'none',
backgroundColor: '#f1f5f9',
border: '1px solid #e9eef5',
backgroundColor: '#ffffff',
borderRadius: 10,
}}
size="middle"
>
<Option value="video">
<VideoCameraOutlined style={{ marginRight: 6, fontSize: 14, color: '#6366f1' }} />
<span style={{ fontSize: 13, fontWeight: 500, color: '#6366f1' }}></span>
<span style={{ fontSize: 13, fontWeight: 600, color: '#0ea5e9' }}></span>
</Option>
<Option value="image">
<PictureOutlined style={{ marginRight: 6, fontSize: 14, color: '#6366f1' }} />
<span style={{ fontSize: 13, fontWeight: 500, color: '#6366f1' }}></span>
<span style={{ fontSize: 13, fontWeight: 600, color: '#0ea5e9' }}></span>
</Option>
</Select>
@@ -2317,35 +2288,35 @@ const AIChatPage: React.FC = () => {
onClick={() => setShowEngineModal(true)}
className="image-settings-trigger"
style={{
minWidth: 200,
padding: '6px 14px',
height: 36,
minWidth: 174,
padding: '5px 12px',
height: 32,
borderRadius: 10,
border: '1px solid rgba(99, 102, 241, 0.15)',
backgroundColor: 'rgba(241, 245, 249, 0.8)',
border: '1px solid #e9eef5',
backgroundColor: '#ffffff',
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
gap: 6,
transition: 'all 0.2s ease',
boxShadow: '0 1px 3px rgba(99, 102, 241, 0.04)',
boxShadow: '0 1px 2px rgba(15, 23, 42, 0.03)',
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = 'rgba(99, 102, 241, 0.08)';
e.currentTarget.style.borderColor = 'rgba(99, 102, 241, 0.3)';
e.currentTarget.style.boxShadow = '0 2px 8px rgba(99, 102, 241, 0.1)';
e.currentTarget.style.backgroundColor = '#f8fafc';
e.currentTarget.style.borderColor = '#d7e7f2';
e.currentTarget.style.boxShadow = '0 2px 8px rgba(15, 23, 42, 0.06)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = 'rgba(241, 245, 249, 0.8)';
e.currentTarget.style.borderColor = 'rgba(99, 102, 241, 0.15)';
e.currentTarget.style.boxShadow = '0 1px 3px rgba(99, 102, 241, 0.04)';
e.currentTarget.style.backgroundColor = '#ffffff';
e.currentTarget.style.borderColor = '#e9eef5';
e.currentTarget.style.boxShadow = '0 1px 2px rgba(15, 23, 42, 0.03)';
}}
>
<SettingOutlined style={{ fontSize: 14, color: '#6366f1' }} />
<Text style={{
fontSize: 13,
fontWeight: 500,
color: '#6366f1',
color: '#334155',
}}>
{mediaType === 'image' ? enginesele.image?.find((e: any) => e.id === countType)?.name : enginesele.video?.find((e: any) => e.id === countType)?.name || '选择引擎'}
</Text>
@@ -2459,7 +2430,7 @@ const AIChatPage: React.FC = () => {
onClick={() => setReferenceModeDropdownVisible(!referenceModeDropdownVisible)}
className="image-settings-trigger"
style={{
minWidth: 120,
minWidth: 118,
padding: '6px 14px',
height: 36,
borderRadius: 10,
@@ -2470,7 +2441,7 @@ const AIChatPage: React.FC = () => {
alignItems: 'center',
gap: 6,
transition: 'all 0.2s ease',
boxShadow: '0 1px 3px rgba(99, 102, 241, 0.04)',
boxShadow: '0 1px 2px rgba(15, 23, 42, 0.03)',
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = 'rgba(99, 102, 241, 0.08)';
@@ -2487,7 +2458,7 @@ const AIChatPage: React.FC = () => {
<Text style={{
fontSize: 13,
fontWeight: 500,
color: '#6366f1',
color: '#334155',
}}>
{referenceMode === 'universal' ? '全能参考' : '首尾帧'}
</Text>
@@ -2627,7 +2598,7 @@ const AIChatPage: React.FC = () => {
onClick={() => setShowImageSettingsModal(!showImageSettingsModal)}
className="image-settings-trigger"
style={{
minWidth: 220,
minWidth: 156,
padding: '6px 14px',
height: 36,
borderRadius: 10,
@@ -2638,7 +2609,7 @@ const AIChatPage: React.FC = () => {
alignItems: 'center',
gap: 6,
transition: 'all 0.2s ease',
boxShadow: '0 1px 3px rgba(99, 102, 241, 0.04)',
boxShadow: '0 1px 2px rgba(15, 23, 42, 0.03)',
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = 'rgba(99, 102, 241, 0.08)';
@@ -2655,7 +2626,7 @@ const AIChatPage: React.FC = () => {
<Text style={{
fontSize: 13,
fontWeight: 500,
color: '#6366f1',
color: '#334155',
}}>
{selectedRatio === 'auto' ? '智能' : selectedRatio} · {selectedResolution == '2K' ? '2K高清' : '4K超清'} · {width}×{height}
</Text>
@@ -2931,7 +2902,7 @@ const AIChatPage: React.FC = () => {
onClick={() => setShowVideoSettingsModal(!showVideoSettingsModal)}
className="image-settings-trigger"
style={{
minWidth: 220,
minWidth: 156,
padding: '6px 14px',
height: 36,
borderRadius: 10,
@@ -2942,7 +2913,7 @@ const AIChatPage: React.FC = () => {
alignItems: 'center',
gap: 6,
transition: 'all 0.2s ease',
boxShadow: '0 1px 3px rgba(99, 102, 241, 0.04)',
boxShadow: '0 1px 2px rgba(15, 23, 42, 0.03)',
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = 'rgba(99, 102, 241, 0.08)';
@@ -2955,15 +2926,13 @@ const AIChatPage: React.FC = () => {
e.currentTarget.style.boxShadow = '0 1px 3px rgba(99, 102, 241, 0.04)';
}}
>
<LayoutOutlined style={{ fontSize: 14, color: '#6366f1' }} />
<Text style={{
fontSize: 13,
fontWeight: 500,
color: '#6366f1',
}}>
{videoAspectRatio} · {videoDuration}s · {videoResolution}
</Text>
<CaretDownOutlined style={{ fontSize: 10, color: '#6366f1', marginLeft: 'auto' }} />
<LayoutOutlined style={{ fontSize: 14, color: '#334155' }} />
<span style={{ fontSize: 13, fontWeight: 600, color: '#334155' }}>{videoAspectRatio}</span>
<span style={{ width: 1, height: 14, background: '#e5e7eb' }} />
<span style={{ fontSize: 13, fontWeight: 600, color: '#334155' }}>{videoResolution?.toUpperCase?.() || videoResolution}</span>
<span style={{ width: 1, height: 14, background: '#e5e7eb' }} />
<span style={{ fontSize: 13, fontWeight: 600, color: '#334155' }}>{videoDuration}s</span>
<CaretDownOutlined style={{ fontSize: 10, color: '#64748b', marginLeft: 'auto' }} />
</button>
{showVideoSettingsModal && (
@@ -3176,13 +3145,68 @@ const AIChatPage: React.FC = () => {
)}
</div>
)}
{/* 预估积分 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 14px', backgroundColor: 'rgba(99, 102, 241, 0.06)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.1)' }}>
<Text style={{ fontSize: 13, color: '#6366f1', fontWeight: 500 }}></Text>
<Text style={{ fontSize: 14, color: '#6366f1', fontWeight: 600 }}>{getEstimatedCredits()}</Text>
</div>
<button
type="button"
onClick={() => {
setInputValue(`${inputValue}@`);
setMentionVisible(currentMedia.length > 0 && referenceMode === 'universal');
setTimeout(() => mentionInputRef.current?.focus?.(), 0);
}}
style={{
width: 32,
height: 32,
borderRadius: 10,
border: '1px solid #e9eef5',
background: '#ffffff',
color: '#334155',
fontSize: 16,
fontWeight: 700,
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 1px 2px rgba(15, 23, 42, 0.03)',
}}
>
@
</button>
</Space>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flexShrink: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, color: '#64748b' }}>
<span style={{ fontSize: 15, lineHeight: 1, color: '#475569' }}></span>
<Text style={{ fontSize: 13, color: '#64748b', fontWeight: 600 }}>{getEstimatedCredits()}</Text>
</div>
<Button
type="primary"
shape="circle"
icon={<ArrowUpOutlined />}
onClick={handleSend}
disabled={
mediaType === 'video' && referenceMode === 'first_last_frame'
? !inputValue.trim() && !firstFrame
: !inputValue.trim() && currentMedia.length === 0
}
loading={loading}
style={{
flexShrink: 0,
width: 36,
height: 36,
borderRadius: '50%',
background: (
mediaType === 'video' && referenceMode === 'first_last_frame'
? (inputValue.trim() || firstFrame)
: (inputValue.trim() || currentMedia.length > 0)
)
? '#cbd5e1'
: '#d9dee6',
color: '#ffffff',
boxShadow: 'none',
transition: 'all 0.2s ease',
border: 'none',
}}
/>
</div>
</div>
</div>
</Content>
@@ -3194,6 +3218,12 @@ const AIChatPage: React.FC = () => {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0.3; }
}
textarea::placeholder {
color: #9aa7b2 !important;
}
.ant-select-selector {
border-radius: 10px !important;
}
`}</style>
{/* 图片/视频预览弹窗 */}