@@ -64,7 +64,7 @@ const { Text } = Typography;
interface MediaReference {
name : string ;
type : 'image' | 'video' ;
type : 'image' | 'video' | 'audio' ;
url : string ;
duration? : number ;
role? : string ;
@@ -203,16 +203,20 @@ const AIChatPage: React.FC = () => {
} ;
// 根据媒体列表生成标签(图片一、图片二、视频一、视频二...)
const generateMediaLabels = ( media : { type : 'image' | 'video' } [ ] ) = > {
const generateMediaLabels = ( media : { type : 'image' | 'video' | 'audio' } [ ] ) = > {
let imgCount = 0 ;
let vidCount = 0 ;
let audCount = 0 ;
return media . map ( ( m ) = > {
if ( m . type === 'image' ) {
imgCount ++ ;
return ` 图片 ${ imgCount } ` ;
} else {
} else if ( m . type === 'video' ) {
vidCount ++ ;
return ` 视频 ${ vidCount } ` ;
} else {
audCount ++ ;
return ` 音频 ${ audCount } ` ;
}
} ) ;
} ;
@@ -502,7 +506,7 @@ const AIChatPage: React.FC = () => {
}
}
let supportedSizes = ( data as any ) . engine ? . image ? . [ 0 ] ? . supported_sizes || { } ;
let supportedSizes = ( data as any ) . engine ? . image ? . [ 0 ] ? . supportedSizes || { } ;
let supportedResolutions : string [ ] = [ ] ;
let twokwidth : string [ ] = [ ] ;
let fourkwidth : string [ ] = [ ] ;
@@ -721,6 +725,7 @@ const AIChatPage: React.FC = () => {
const handleRemoveFirstFrame = ( ) = > {
setFirstFrame ( null ) ;
setLastFrame ( null ) ;
} ;
const handleRemoveLastFrame = ( ) = > {
@@ -977,7 +982,7 @@ const AIChatPage: React.FC = () => {
} ) ;
} ;
const handleUpload = async ( file : File , frameTarget ? : 'first' | 'last' ) = > {
const handleUpload = async ( file : File , frameTarget ? : 'first' | 'last' | File [ ] ) = > {
const isImage = file . type . startsWith ( 'image/' ) ;
const isVideo = file . type . startsWith ( 'video/' ) ;
@@ -1020,8 +1025,10 @@ const AIChatPage: React.FC = () => {
return false ;
}
if ( ! isImage && ! isVideo ) {
message . error ( '仅支持图片或视频文件' ) ;
const isAudio = file . type . startsWith ( 'audio/' ) ;
if ( ! isImage && ! isVideo && ! isAudio ) {
message . error ( '仅支持图片、视频或音频文件' ) ;
return false ;
}
@@ -1030,14 +1037,15 @@ const AIChatPage: React.FC = () => {
const maxImage = currentEngine ? . maxImageCount ? ? 4 ;
const maxVideo = currentEngine ? . maxVideoCount ? ? 1 ;
if ( mediaType === 'image' && isVideo ) {
if ( mediaType === 'image' && ( isVideo || isAudio ) ) {
message . error ( '图片模式仅支持上传图片' ) ;
return false ;
}
const maxMB = isVideo ? 100 : 10 ;
const maxMB = isVideo ? 100 : ( isAudio ? 50 : 10 ) ;
const fileTypeText = isVideo ? '视频' : ( isAudio ? '音频' : '图片' ) ;
if ( file . size / 1024 / 1024 > maxMB ) {
message . error ( ` ${ isVideo ? '视频' : '图片' } 大小不能超过 ${ maxMB } MB ` ) ;
message . error ( ` ${ fileTypeText } 大小不能超过 ${ maxMB } MB ` ) ;
return false ;
}
@@ -1077,19 +1085,19 @@ const AIChatPage: React.FC = () => {
setUploading ( true ) ;
try {
const uploadFn = isImage ? uploadImage : uploadVideo ;
const uploadFn = isImage ? uploadImage : ( isAudio ? uploadImage : uploadVideo ) ;
const res = await uploadFn ( file ) ;
const mediaType : 'image' | 'video' = isImage ? 'image' : 'video' ;
const mediaType : 'image' | 'video' | 'audio' = isImage ? 'image' : ( isAudio ? 'audio' : 'video' ) ;
const newList = [ . . . currentMedia , {
name : file.name ,
type : mediaType ,
url : res.url ,
label : '' ,
. . . ( isVideo && { duration : videoDuration } ) ,
. . . ( ( isVideo || isAudio ) && { duration : videoDuration } ) ,
} ] ;
const labels = generateMediaLabels ( newList ) ;
setCurrentMedia ( newList . map ( ( m , i ) = > ( { . . . m , label : labels [ i ] } ) ) ) ;
message . success ( ` ${ isImage ? '图片' : '视频' } 上传成功 ` ) ;
message . success ( ` ${ isImage ? '图片' : ( isAudio ? '音频' : '视频' ) } 上传成功 ` ) ;
} catch ( error ) {
message . error ( '上传失败' ) ;
} finally {
@@ -1247,7 +1255,7 @@ const AIChatPage: React.FC = () => {
? '输入画面描述,或上传图片参考风格、构图、主体与光影。例:@图片1 参考色调,生成一张简洁高级的产品海报。'
: isFirstLastFrameComposer
? '描述首帧到尾帧的主体动作、镜头运动、节奏与转场。例:从首帧自然推进到尾帧,产品居中突出,动作平滑连贯。'
: '上传最多12个参考素材,输入文字或 @ 引用参考内容,自由组合图、文、音、 视频。例:@图片1 模仿 @视频1 的动作,音色参考 @音频1 。' ;
: '上传最多12个参考素材,输入文字或 @ 引用参考内容,自由组合图、文、视频。例:@图片1 模仿 @视频1 的动作。' ;
const composerHelperText = mediaType === 'image'
? '图片参考 · 适合海报、产品图、场景图与风格图生成'
: isFirstLastFrameComposer
@@ -1259,11 +1267,8 @@ const AIChatPage: React.FC = () => {
margin : '-24px -32px -32px' ,
borderRadius : 22 ,
height : 'calc(100vh - 34px)' ,
background : 'linear-gradient(180deg, #ffffff 0%, #ffffff 100%) ' ,
backgroundRepeat : 'no-repeat' ,
backgroundSize : '100% 100%' ,
backgroundPosition : 'center' ,
backgroundBlendMode : 'normal' ,
background : '#fff ' ,
overflow : 'hidden' ,
} } >
{ /* 左侧边栏 - 对话列表(已隐藏,保留代码) */ }
@@ -1353,15 +1358,15 @@ const AIChatPage: React.FC = () => {
) }
{ /* 主内容区 */ }
< Layout style = { { display : 'flex' , flex : 1 , background : 'transparent ' } } >
< Layout style = { { display : 'flex' , flex : 1 , background : '#fff ' } } >
{ /* 头部 - 显示对话标题和模型信息 */ }
< div className = "animate-fadeInUp" style = { {
display : 'flex' , justifyContent : 'space-between' , alignItems : 'center' ,
marginBottom : 18 , padding : '16px 24px' , borderRadius : 22 ,
background : 'rgba (255, 255, 255, 0.92 )' ,
padding : '16px 24px' , borderRadius : 22 ,
background : 'rgb(255, 255, 255)' ,
backdropFilter : 'blur(22px)' ,
border : '1px solid rgba(231, 234, 240, 0.82)' ,
boxShadow : '0 16px 44px rgba(31, 41, 55, 0.06)' ,
border : '1px solid rgba(231, 234, 240, 0.82)' ,
// boxShadow: '0 16px 44px rgba(31, 41, 55, 0.06)',
position : 'relative' , overflow : 'hidden' , flexWrap : 'wrap' , gap : 12 ,
} } >
< div style = { { width : '100%' , display : 'flex' , alignItems : 'center' , gap : 16 } } >
@@ -1434,8 +1439,10 @@ const AIChatPage: React.FC = () => {
overflowY : 'auto' ,
paddingRight : 10 ,
paddingBottom : 18 ,
background : 'linear-gradient(180deg, rgba(255,255,255,0.72), rgba(255,255,255,0))' ,
borderRadius : 22 ,
// background: 'linear-gradient(180deg, rgba(255,255,255,0.72), rgba(255,255,255,0))',
background : '#fff' ,
// borderRadius: 22,
} }
>
{ /* 加载更多按钮 - 在列表顶部 */ }
@@ -1483,7 +1490,7 @@ const AIChatPage: React.FC = () => {
alignItems : 'center' ,
justifyContent : 'center' ,
flexShrink : 0 ,
boxShadow : '0 8px 18px rgba(47, 52, 64, 0.15)' ,
// boxShadow: '0 8px 18px rgba(47, 52, 64, 0.15)' ,
} }
>
< RobotOutlined style = { { color : '#fff' , fontSize : 16 } } / >
@@ -1506,7 +1513,7 @@ const AIChatPage: React.FC = () => {
boxSizing : 'border-box' ,
boxShadow : '0 18px 50px rgba(47, 52, 64, 0.075)' ,
position : 'relative' ,
border : '1px solid rgba(231, 234, 240, 0.9)' ,
border : '1px solid rgba(231, 234, 240, 0.9)' ,
} }
>
@@ -1696,7 +1703,8 @@ const AIChatPage: React.FC = () => {
{ /* 根据 status 显示不同内容 */ }
{ ( msg . status === 'generating' || msg . status === 'failed' || msg . status === 'completed' ) && (
< div style = { { display : 'flex' , gap : 16 , marginBottom : 16 , marginTop : 16 , width : '100%' , alignItems : 'flex-start' ,
< div style = { {
display : 'flex' , gap : 16 , marginBottom : 16 , marginTop : 16 , width : '100%' , alignItems : 'flex-start' ,
} } >
< div style = { { width : '50%' , overflow : 'hidden' , borderRadius : 12 , position : 'relative' , height : 220 , border : '1px solid #E7EAF0' , boxShadow : '0 4px 12px rgba(139, 92, 246, 0.08)' , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , background : 'linear-gradient(135deg, #ffffff 0%, #FAFBFC 100%)' } } >
@@ -1746,19 +1754,22 @@ const AIChatPage: React.FC = () => {
< / div >
< div style = { { width : '50%' , display : 'flex' , flexDirection : 'column' , gap : 12 } } >
< div style = { { background : '#FFFFFF' , borderRadius : 12 , padding : 12 , border : '1px solid #E7EAF0' , flex : 1 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' } } >
< p style = { { width : '100%' , height : 150 , overflow : 'auto' , padding : 0 , marginTop : 0 , marginBottom : 0 , lineHeight : 2 , color : '#475467' , fontSize : 13 } } > { msg . originalPrompt } < / p >
< p style = { { width : '100%' , height : 150 , overflow : 'auto' , padding : 0 , marginTop : 0 , marginBottom : 0 , lineHeight : 2 , color : '#475467' , fontSize : 13 } } > { msg . originalPrompt } < / p >
< / div >
< div style = { { fontSize : 12 , color : '#667085' , textAlign : 'left' , display : 'flex' , flexWrap : 'wrap' , gap : 12 , alignItems : 'center' , justifyContent : 'space-between' , marginBottom : 12 } } >
< div style = { { fontSize : 12 , color : '#667085' , textAlign : 'left' , display : 'flex' , flexWrap : 'wrap' , gap : 12 , alignItems : 'center' , justifyContent : 'space-between' , marginBottom : 12 } } >
< span style = { {
// background: 'rgba(139, 92, 246, 0.08)',
borderRadius : 16 , color : '#8b5cf6' , fontWeight : 500 } } > { msg . engineSnapshot . name } < / span >
borderRadius : 16 , color : '#8b5cf6' , fontWeight : 500
} } > { msg . engineSnapshot . name } < / span >
< span style = { {
// background: 'rgba(139, 92, 246, 0.08)',
borderRadius : 16 , color : '#667085' } } > { msg . genType === 'image' ? ` ${ msg . imageProportion || '' } · ${ msg . imagePx || '' } · ${ msg . imageSize || '' } ` : ` ${ msg . duration || '' } s · ${ msg . aspectRatio || '' } · ${ msg . resolution || '' } ` } < / span >
borderRadius : 16 , color : '#667085'
} } > { msg . genType === 'image' ? ` ${ msg . imageProportion || '' } · ${ msg . imagePx || '' } · ${ msg . imageSize || '' } ` : ` ${ msg . duration || '' } s · ${ msg . aspectRatio || '' } · ${ msg . resolution || '' } ` } < / span >
< span style = { {
// background: 'rgba(164, 91, 91, 0.08)',
fontSize : 14 ,
borderRadius : 16 , color : '#8b5cf6' } } > 消 耗 积 分 : { msg . creditsCost } < / span >
borderRadius : 16 , color : '#8b5cf6'
} } > 消 耗 积 分 : { msg . creditsCost } < / span >
< / div >
@@ -1828,7 +1839,7 @@ const AIChatPage: React.FC = () => {
< span style = { { fontSize : 11 , color : '#98A2B3' , fontWeight : 600 } } > 点 击 素 材 查 看 , 或 直 接 下 载 原 文 件 < / span >
< / div >
< div style = { { display : 'flex' , alignItems : 'center' , gap : 8 } } >
< span style = { { fontSize : 11 , color : '#98A2B3' , fontWeight : 700 } } > { attachmentRefs . length } / 12 < / span >
{ /* <span style={{ fontSize: 11, color: '#98A2B3', fontWeight: 700 }}>{attachmentRefs.length}/12</span> */ }
< button
onClick = { ( ) = > {
setAttachmentPopupVisible ( false ) ;
@@ -1917,9 +1928,9 @@ const AIChatPage: React.FC = () => {
< / div >
< / >
) }
< span style = { { position : 'absolute' , left : 6 , top : 6 , padding : '2px 6px' , borderRadius : 999 , background : 'rgba(255,255,255,0.92)' , color : '#8b5cf6' , fontSize : 10 , fontWeight : 800 , boxShadow : '0 4px 10px rgba(31, 41, 55, 0.08)' } } >
{ /* <span style={{ position: 'absolute', left: 6, top: 6, padding: '2px 6px', borderRadius: 999, background: 'rgba(255,255,255,0.92)', color: '#8b5cf6', fontSize: 10, fontWeight: 800, boxShadow: '0 4px 10px rgba(31, 41, 55, 0.08)' }}>
{label}
< / span >
</span> */ }
< / div >
< div title = { ref . name || label } style = { { marginTop : 6 , color : '#667085' , fontSize : 10 , fontWeight : 700 , overflow : 'hidden' , textOverflow : 'ellipsis' , whiteSpace : 'nowrap' , textAlign : 'center' } } >
@@ -1997,18 +2008,17 @@ const AIChatPage: React.FC = () => {
{ /* 上方输入布局 */ }
< div style = { { display : 'flex' , gap : isFirstLastFrameComposer ? 18 : 18 , alignItems : 'flex-start' , marginBottom : 14 } } >
{ /* 左侧附件区域 */ }
< div style = { { width : isFirstLastFrameComposer ? 376 : 128 , minWidth : isFirstLastFrameComposer ? 376 : 128 , display : 'flex' , flexDirection : 'column' , alignItems : 'center' , justifyContent : 'flex-start' , paddingTop : 2 } } >
< div style = { { width : isFirstLastFrameComposer ? 240 : 128 , minWidth : isFirstLastFrameComposer ? 240 : 128 , display : 'flex' , flexDirection : 'column' , alignItems : 'center' , justifyContent : 'flex-start' , paddingTop : 2 } } >
{ /* 首尾帧模式 */ }
{ mediaType === 'video' && referenceMode === 'first_last_frame' ? (
< div style = { { display : 'flex' , flexDirection : 'row' , alignItems : 'stretch' , gap : 10 , width : '100%' , height : 92 } } >
< div style = { { display : 'flex' , flexDirection : 'row' , alignItems : 'stretch' , gap : 8 , width : '100%' , height : 76 } } >
{ /* 首帧 */ }
< div style = { { position : 'relative' , flex : 1 , minWidth : 0 , height : 92 , borderRadius : 16 , border : '1px solid #E7EAF0' , background : '#ffffff' , boxShadow : '0 8 px 20 px rgba(47, 52, 64, 0.045 )' , overflow : 'hidden' } } >
< div style = { { position : 'absolute' , top : 8 , left : 10 , right : 10 , display : 'flex' , alignItems : 'center' , justifyContent : 'space-between' , zIndex : 2 } } >
< span style = { { fontSize : 11 , fontWeight : 700 , color : '#8b5cf6' , lineHeight : 1 } } > 首 帧 < / span >
< span style = { { fontSize : 10 , color : '#98A2B3' } } > 必 传 < / span >
< div style = { { position : 'relative' , flex : 1 , minWidth : 0 , height : 76 , borderRadius : 12 , border : '1px solid #E7EAF0' , background : '#ffffff' , boxShadow : '0 6 px 16 px rgba(47, 52, 64, 0.04)' , overflow : 'hidden' } } >
< div style = { { position : 'absolute' , top : 6 , left : 8 , right : 8 , display : 'flex' , alignItems : 'center' , justifyContent : 'space-between' , zIndex : 2 } } >
< span style = { { fontSize : 10 , fontWeight : 600 , color : '#8b5cf6' , lineHeight : 1 } } > 首 帧 < / span >
< / div >
{ firstFrame ? (
< div style = { { position : 'absolute' , left : 10 , right : 10 , bottom : 10 , height : 54 } } >
< div style = { { position : 'absolute' , left : 8 , right : 8 , bottom : 8 , height : 44 } } >
< img
src = { ` ${ import . meta . env . VITE_API_BASE || "http://localhost:8000" } ${ firstFrame . url } ` }
alt = { firstFrame . name }
@@ -2018,24 +2028,24 @@ const AIChatPage: React.FC = () => {
setAttachmentPreviewName ( firstFrame . name ) ;
setAttachmentPreviewVisible ( true ) ;
} }
style = { { width : '100%' , height : '100%' , objectFit : 'cover' , borderRadius : 10 , cursor : 'pointer' , border : '1px solid rgba(139, 92, 246, 0.2)' , boxShadow : '0 4 px 12 px rgba(47, 52, 64, 0.08 )' } }
style = { { width : '100%' , height : '100%' , objectFit : 'cover' , borderRadius : 8 , cursor : 'pointer' , border : '1px solid rgba(139, 92, 246, 0.2)' , boxShadow : '0 3 px 10 px rgba(47, 52, 64, 0.06 )' } }
/ >
< button
onClick = { handleRemoveFirstFrame }
style = { { position : 'absolute' , top : - 7 , right : - 7 , width : 20 , height : 20 , border : 'none' , background : '#A45B5B' , borderRadius : 50 , cursor : 'pointer' , color : '#fff' , fontSize : 10 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , boxShadow : '0 2px 6 px rgba(164, 91, 91, 0.35 )' , zIndex : 10 } }
style = { { position : 'absolute' , top : - 6 , right : - 6 , width : 18 , height : 18 , border : 'none' , background : '#A45B5B' , borderRadius : 50 , cursor : 'pointer' , color : '#fff' , fontSize : 9 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , boxShadow : '0 2px 4 px rgba(164, 91, 91, 0.3)' , zIndex : 10 } }
>
< DeleteOutlined style = { { fontSize : 10 } } / >
< DeleteOutlined style = { { fontSize : 9 } } / >
< / button >
< / div >
) : (
< Upload accept = "image/*" showUploadList = { false } beforeUpload = { ( file ) = > handleUpload ( file , 'first' ) } >
< div
style = { { position : 'absolute' , left : 10 , right : 10 , bottom : 10 , height : 54 , borderRadius : 10 , border : '1px dashed rgba(139, 92, 246, 0.28)' , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , cursor : 'pointer' , transition : 'all 0.25s ease' , backgroundColor : 'rgba(139, 92, 246, 0.04)' , flexDirection : 'column' , gap : 3 } }
style = { { position : 'absolute' , left : 8 , right : 8 , bottom : 8 , height : 44 , borderRadius : 8 , border : '1px dashed rgba(139, 92, 246, 0.28)' , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , cursor : 'pointer' , transition : 'all 0.25s ease' , backgroundColor : 'rgba(139, 92, 246, 0.04)' , flexDirection : 'column' , gap : 2 } }
onMouseEnter = { ( e ) = > { e . currentTarget . style . borderColor = '#a78bfa' ; e . currentTarget . style . backgroundColor = 'rgba(139, 92, 246, 0.08)' ; } }
onMouseLeave = { ( e ) = > { e . currentTarget . style . borderColor = 'rgba(139, 92, 246, 0.28)' ; e . currentTarget . style . backgroundColor = 'rgba(139, 92, 246, 0.04)' ; } }
>
< PlusOutlined style = { { fontSize : 16 , color : '#8b5cf6' } } / >
< span style = { { fontSize : 11 , color : '#8b5cf6' , fontWeight : 600 } } > 上 传 首 帧 < / span >
< PlusOutlined style = { { fontSize : 14 , color : '#8b5cf6' } } / >
< span style = { { fontSize : 10 , color : '#8b5cf6' , fontWeight : 500 } } > 上 传 首 帧 < / span >
< / div >
< / Upload >
) }
@@ -2046,21 +2056,20 @@ const AIChatPage: React.FC = () => {
onClick = { handleSwapFrames }
disabled = { ! firstFrame || ! lastFrame }
title = "调换首尾帧"
style = { { width : 34 , height : 34 , alignSelf : 'center' , borderRadius : 50 , border : '1px solid rgba(231, 234, 240, 0.95)' , background : firstFrame && lastFrame ? 'linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%)' : '#F3F5F8' , cursor : firstFrame && lastFrame ? 'pointer' : 'not-allowed' , color : firstFrame && lastFrame ? '#fff' : '#98A2B3' , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , boxShadow : firstFrame && lastFrame ? '0 8 px 18 px rgba(139, 92, 246, 0.28 )' : 'none' , transition : 'all 0.2s ease' , flexShrink : 0 } }
style = { { width : 28 , height : 28 , alignSelf : 'center' , borderRadius : 50 , border : '1px solid rgba(231, 234, 240, 0.95)' , background : firstFrame && lastFrame ? 'linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%)' : '#F3F5F8' , cursor : firstFrame && lastFrame ? 'pointer' : 'not-allowed' , color : firstFrame && lastFrame ? '#fff' : '#98A2B3' , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , boxShadow : firstFrame && lastFrame ? '0 6 px 14 px rgba(139, 92, 246, 0.24 )' : 'none' , transition : 'all 0.2s ease' , flexShrink : 0 } }
onMouseEnter = { ( e ) = > { if ( firstFrame && lastFrame ) e . currentTarget . style . transform = 'scale(1.06)' ; } }
onMouseLeave = { ( e ) = > { e . currentTarget . style . transform = 'scale(1)' ; } }
>
< SwapOutlined style = { { fontSize : 14 } } / >
< SwapOutlined style = { { fontSize : 12 } } / >
< / button >
{ /* 尾帧 */ }
< div style = { { position : 'relative' , flex : 1 , minWidth : 0 , height : 92 , borderRadius : 16 , border : '1px solid #E7EAF0' , background : '#ffffff' , boxShadow : '0 8 px 20 px rgba(47, 52, 64, 0.045 )' , overflow : 'hidden' } } >
< div style = { { position : 'absolute' , top : 8 , left : 10 , right : 10 , display : 'flex' , alignItems : 'center' , justifyContent : 'space-between' , zIndex : 2 } } >
< span style = { { fontSize : 11 , fontWeight : 700 , color : '#8b5cf6' , lineHeight : 1 } } > 尾 帧 < / span >
< span style = { { fontSize : 10 , color : '#98A2B3' } } > 可 选 < / span >
< div style = { { position : 'relative' , flex : 1 , minWidth : 0 , height : 76 , borderRadius : 12 , border : '1px solid #E7EAF0' , background : '#ffffff' , boxShadow : '0 6 px 16 px rgba(47, 52, 64, 0.04)' , overflow : 'hidden' , opacity : ! firstFrame ? 0.5 : 1 } } >
< div style = { { position : 'absolute' , top : 6 , left : 8 , right : 8 , display : 'flex' , alignItems : 'center' , justifyContent : 'space-between' , zIndex : 2 } } >
< span style = { { fontSize : 10 , fontWeight : 600 , color : '#8b5cf6' , lineHeight : 1 } } > 尾 帧 < / span >
< / div >
{ lastFrame ? (
< div style = { { position : 'absolute' , left : 10 , right : 10 , bottom : 10 , height : 54 } } >
< div style = { { position : 'absolute' , left : 8 , right : 8 , bottom : 8 , height : 44 } } >
< img
src = { ` ${ import . meta . env . VITE_API_BASE || "http://localhost:8000" } ${ lastFrame . url } ` }
alt = { lastFrame . name }
@@ -2070,29 +2079,38 @@ const AIChatPage: React.FC = () => {
setAttachmentPreviewName ( lastFrame . name ) ;
setAttachmentPreviewVisible ( true ) ;
} }
style = { { width : '100%' , height : '100%' , objectFit : 'cover' , borderRadius : 10 , cursor : 'pointer' , border : '1px solid rgba(139, 92, 246, 0.2)' , boxShadow : '0 4 px 12 px rgba(47, 52, 64, 0.08 )' } }
style = { { width : '100%' , height : '100%' , objectFit : 'cover' , borderRadius : 8 , cursor : 'pointer' , border : '1px solid rgba(139, 92, 246, 0.2)' , boxShadow : '0 3 px 10 px rgba(47, 52, 64, 0.06 )' } }
/ >
< button
onClick = { handleRemoveLastFrame }
style = { { position : 'absolute' , top : - 7 , right : - 7 , width : 20 , height : 20 , border : 'none' , background : '#A45B5B' , borderRadius : 50 , cursor : 'pointer' , color : '#fff' , fontSize : 10 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , boxShadow : '0 2px 6 px rgba(164, 91, 91, 0.35 )' , zIndex : 10 } }
style = { { position : 'absolute' , top : - 6 , right : - 6 , width : 18 , height : 18 , border : 'none' , background : '#A45B5B' , borderRadius : 50 , cursor : 'pointer' , color : '#fff' , fontSize : 9 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , boxShadow : '0 2px 4 px rgba(164, 91, 91, 0.3)' , zIndex : 10 } }
>
< DeleteOutlined style = { { fontSize : 10 } } / >
< DeleteOutlined style = { { fontSize : 9 } } / >
< / button >
< / div >
) : (
firstFrame ? (
< Upload accept = "image/*" showUploadList = { false } beforeUpload = { ( file ) = > handleUpload ( file , 'last' ) } >
< div
style = { { position : 'absolute' , left : 10 , right : 10 , bottom : 10 , height : 54 , borderRadius : 10 , border : '1px dashed rgba(139, 92, 246, 0.24)' , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , cursor : 'pointer' , transition : 'all 0.25s ease' , backgroundColor : 'rgba(139, 92, 246, 0.04)' , flexDirection : 'column' , gap : 3 } }
style = { { position : 'absolute' , left : 8 , right : 8 , bottom : 8 , height : 44 , borderRadius : 8 , border : '1px dashed rgba(139, 92, 246, 0.24)' , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , cursor : 'pointer' , transition : 'all 0.25s ease' , backgroundColor : 'rgba(139, 92, 246, 0.04)' , flexDirection : 'column' , gap : 2 } }
onMouseEnter = { ( e ) = > { e . currentTarget . style . borderColor = '#a78bfa' ; e . currentTarget . style . backgroundColor = 'rgba(139, 92, 246, 0.08)' ; } }
onMouseLeave = { ( e ) = > { e . currentTarget . style . borderColor = 'rgba(139, 92, 246, 0.24)' ; e . currentTarget . style . backgroundColor = 'rgba(139, 92, 246, 0.04)' ; } }
>
< PlusOutlined style = { { fontSize : 16 , color : '#8b5cf6' } } / >
< span style = { { fontSize : 11 , color : '#8b5cf6' , fontWeight : 600 } } > 上 传 尾 帧 < / span >
< PlusOutlined style = { { fontSize : 14 , color : '#8b5cf6' } } / >
< span style = { { fontSize : 10 , color : '#8b5cf6' , fontWeight : 500 } } > 上 传 尾 帧 < / span >
< / div >
< / Upload >
) : (
< div
style = { { position : 'absolute' , left : 8 , right : 8 , bottom : 8 , height : 44 , borderRadius : 8 , border : '1px dashed rgba(226, 232, 240, 0.4)' , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , cursor : 'not-allowed' , backgroundColor : 'rgba(241, 245, 249, 0.5)' , flexDirection : 'column' , gap : 2 } }
>
< PlusOutlined style = { { fontSize : 14 , color : '#cbd5e1' } } / >
< span style = { { fontSize : 10 , color : '#94a3b8' , fontWeight : 500 } } > 请 先 上 传 首 帧 < / span >
< / div >
)
) }
< / div >
< / div > ) : (
< / div > ) : (
< >
{ currentMedia . length > 0 ? (
< div
@@ -2215,7 +2233,7 @@ const AIChatPage: React.FC = () => {
>
< 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 } / 12 < / 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 ) = > (
@@ -2250,7 +2268,7 @@ const AIChatPage: React.FC = () => {
< / span >
< button
onClick = { ( ) = > handleRemoveMedia ( idx ) }
style = { { position : 'absolute' , top : - 6 , 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 } }
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 >
@@ -2266,7 +2284,7 @@ const AIChatPage: React.FC = () => {
{ ! ( mediaType === 'video' && referenceMode === 'first_last_frame' ) && (
< div style = { { marginTop : currentMedia.length > 0 ? 8 : 0 , width : '100%' , display : 'flex' , justifyContent : 'center' , minHeight : currentMedia.length > 0 ? 82 : 84 } } >
< Upload
accept = { mediaType === 'image' ? 'image/*' : 'image/*,video/*' }
accept = { mediaType === 'image' ? 'image/*' : 'image/*,video/*,audio/* ' }
showUploadList = { false }
beforeUpload = { handleUpload }
>
@@ -2313,7 +2331,7 @@ const AIChatPage: React.FC = () => {
< span style = { { display : 'flex' , flexDirection : 'column' , alignItems : 'center' , gap : 1 } } > < span > 参 < / span > < span > 考 < / span > < / span >
< span style = { { display : 'flex' , flexDirection : 'column' , alignItems : 'center' , gap : 1 } } > < span > 内 < / span > < span > 容 < / span > < / span >
< / div >
< span style = { { fontSize : 9 , color : '#98a2b3' , fontWeight : 600 , lineHeight : 1 } } > { currentMedia . length } / 12 < / span >
{ /* <span style={{ fontSize: 9, color: '#98a2b3', fontWeight: 600, lineHeight: 1 }}>{currentMedia.length}/{maxImageCount + maxVideoCount}</span> */ }
< / >
) }
< / div >
@@ -2341,12 +2359,12 @@ const AIChatPage: React.FC = () => {
position : 'relative' ,
minHeight : isFirstLastFrameComposer ? 92 : 74 ,
} } >
{ ! inputValue . trim ( ) && (
{ /* {!inputValue.trim() && (
<div style={{ display: 'flex', alignItems: 'center', gap: 10, minHeight: 22, paddingTop: 1 }}>
<span style={{ fontSize: 13, fontWeight: 800, color: '#475467', letterSpacing: 0.1, whiteSpace: 'nowrap' }}>{composerModeLabel}</span>
<span style={{ fontSize: 13, color: '#667085', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontWeight: 500 }}>{composerHelperText}</span>
</div>
) }
)} */ }
{ /* 文本输入框 */ }
< TextArea
ref = { mentionInputRef }
@@ -2369,7 +2387,8 @@ const AIChatPage: React.FC = () => {
fontWeight : 500 ,
resize : 'none' ,
backgroundColor : 'transparent' ,
padding : inputValue.trim ( ) ? '4px 0 0' : '2px 0 0' ,
padding : 10 ,
} }
disabled = { loading }
/ >
@@ -2434,19 +2453,23 @@ const AIChatPage: React.FC = () => {
{ /* 底部工具栏 */ }
< div style = { { display : 'flex' , justifyContent : 'space-between' , alignItems : 'center' , gap : 14 , marginTop : 10 , paddingTop : 13 , borderTop : '1px solid rgba(231, 234, 240, 0.76)' } } >
< Space size = { 8 } wrap style = { { flex : 1 , minWidth : 0 } } >
< span >
创 作 类 型 :
< / span >
{ /* 文件类型选择器 */ }
< Select
className = "ai-composer-select"
value = { mediaType }
onChange = { ( val ) = > setMediaType ( val ) }
style = { {
width : 118 ,
minWidth : 118 ,
padding : '6px 14px' ,
height : 32 ,
outline : 'none' ,
border : '1px solid rgba(226, 233, 244, 0.95) ' ,
backgroundColor : 'rgba(255,255,255,0.86) ' ,
borderRadius : 11 ,
boxShadow : '0 4 px 1 2px rgba(47, 52, 64, 0.05 5)' ,
border : '1px solid #E7EAF0 ' ,
backgroundColor : '#ffffff ' ,
borderRadius : 10 ,
boxShadow : '0 1 px 2px rgba(47, 52, 64, 0.02 5)' ,
} }
size = "middle"
>
@@ -2463,7 +2486,7 @@ const AIChatPage: React.FC = () => {
{ /* 引擎选择器 */ }
< div style = { { position : 'relative' , display : 'inline-block' } } >
< button
onClick = { ( ) = > setShowEngineModal ( true ) }
onClick = { ( ) = > setShowEngineModal ( prev = > ! prev ) }
className = "image-settings-trigger"
style = { {
minWidth : 174 ,
@@ -2581,7 +2604,7 @@ const AIChatPage: React.FC = () => {
} }
>
< span style = { {
fontSize : 13 ,
fontSize : 14 ,
fontWeight : countType === engine . id ? 600 : 500 ,
color : countType === engine . id ? '#8b5cf6' : '#475467' ,
marginBottom : 2 ,