素材案例对接,资源存储修改

This commit is contained in:
sjy
2026-07-01 17:54:25 +08:00
parent 04f34c1075
commit 13ede48105
6 changed files with 199 additions and 115 deletions
+2 -46
View File
@@ -305,51 +305,7 @@ export async function getCreditRatios(): Promise<any[]> {
return api.get('/credits/ratios');
}
// 引擎配置
export async function getEngine(): Promise<any> {
if (USE_MOCK) return {
engine: {
image: [
{
id: "0019e3dac0b795b925b",
name: "Seedream 5.0",
provider: "ark",
model_name: "doubao-seedream-5-0-260128",
supported_models: ["doubao-seedream-5-0-260128"],
supported_sizes: {
"2K": {
"1:1": "2048×2048",
"4:3": "2304×1728",
"3:4": "1728×2304",
"16:9": "2560×1440",
"9:16": "1600×2848",
"3:2": "2496×1664",
"2:3": "1664×2496",
"21:9": "3024×1296"
},
"4K": {
"1:1": "4096×4096",
"4:3": "4608×3456",
"3:4": "3520×4704",
"16:9": "5404×3040",
"9:16": "3040×5504",
"3:2": "4992×3328",
"2:3": "3328×4992",
"21:9": "6197×2656"
}
},
default_size: "2K",
priority: 10,
maxImageCount: 4,
maxVideoCount: 0
}
],
video: [
{ 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 }
]
}
};
export async function getEngine(): Promise<any[]> {
return api.get('/generation-ai/engines');
}
// ── Generation AI Tasks ────────────────────────────────────
@@ -760,6 +716,6 @@ export async function getHomeCaseHeader(): Promise<any> {
}
// 首页素材按钮资源
export async function getHomeCaseButton(id: string,limit:number=8): Promise<any> {
export async function getHomeCaseButton(id: string,limit:number=5): Promise<any> {
return api.get(`/home-materials?category_id=${id}&limit_per_category=${limit}&include_empty_categories=false&response_mode=grouped&page=1&page_size=20`);
}
@@ -165,9 +165,10 @@ const StorageCard: React.FC<{ data: ResourceCapacityData | null }> = ({ data })
width: '100%',
height: 20,
background: '#e2e8f0',
borderRadius: 3,
borderRadius: 2,
overflow: 'hidden',
position: 'relative',
}}
>
<div style={{
@@ -182,17 +183,16 @@ const StorageCard: React.FC<{ data: ResourceCapacityData | null }> = ({ data })
justifyContent: 'space-between',
marginBottom: 6,
fontSize: 12,
color: '#000000ff',
color: isOver ? '#ffffffff' : '#000000ff',
padding: '0 8px',
}}>
<span style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<DatabaseOutlined style={{ fontSize: 12, color: '#000000ff' }} />
<DatabaseOutlined style={{ fontSize: 12, color: isOver ? '#ffffffff' :'#000000ff' }} />
{rawPercent.toFixed(1)}%
</span>
{data.enabled ? (
<span style={{ fontWeight: 500, color: isOver ? '#ef4444' : '#000000ff' }}>
<span style={{ fontWeight: 500, color: isOver ? '#ffffffff' : '#000000ff' }}>
{used.toFixed(2)} / {total.toFixed(2)} {unit}
</span>
) : (
@@ -207,10 +207,13 @@ const StorageCard: React.FC<{ data: ResourceCapacityData | null }> = ({ data })
style={{
width: `${barPercent}%`,
height: '100%',
background: isOver
? 'linear-gradient(90deg, #ef4444, #dc2626)'
: 'linear-gradient(90deg, #6366f1, #8b5cf6)',
borderRadius: 3,
background: rawPercent < 50
? 'linear-gradient(90deg, #279951, #b7fad0)'
: rawPercent < 85
? 'linear-gradient(90deg, #ffd759, #fff6d4)'
: 'linear-gradient(90deg, #ef4444, #dc2626)',
borderRadius: 2,
transition: 'width 0.4s ease',
}}
/>
+178 -56
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { Button, Input, Tabs, Tag, Upload, message } from 'antd';
import React, { useEffect, useState, useRef } from 'react';
import { Button, Input, Tabs, Tag, Upload, message, Modal } from 'antd';
import {
FileTextOutlined,
ScissorOutlined,
@@ -9,6 +9,7 @@ import {
VideoCameraOutlined,
PictureOutlined,
ThunderboltOutlined,
PlayCircleOutlined,
} from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { getmedit ,getHomeCaseHeader,getHomeCaseButton} from '../api';
@@ -39,13 +40,28 @@ const HomePage: React.FC = () => {
const [activeTab, setActiveTab] = useState('project');
const [inputValue, setInputValue] = useState('');
const [mockVideos, setMockVideos] = useState<any[]>([]);
const [caseHeader, setCaseHeader] = useState<any[]>([]);
const [activeCaseTab, setActiveCaseTab] = useState<string>('');
const [caseAssets, setCaseAssets] = useState<any[]>([]);
const [previewAsset, setPreviewAsset] = useState<any>(null);
const previewVideoRef = useRef<HTMLVideoElement>(null);
useEffect(() => {
getHomeCaseHeader().then((res) => {
console.log(res);
})
getHomeCaseButton('0019f17d00fdbece98e').then((res) => {
console.log(res);
})
getHomeCaseHeader().then((res: any) => {
// console.log('caseHeader:', res);
if (res?.items?.length > 0) {
setCaseHeader(res.items);
const firstId = res.items[0].id;
setActiveCaseTab(firstId);
// 初始请求第一个 tab 的数据
getHomeCaseButton(firstId).then((btnRes: any) => {
console.log('caseButton:', btnRes);
if (btnRes?.categories?.[0]?.assets) {
setCaseAssets(btnRes.categories[0].assets);
}
});
}
});
}, []);
useEffect(() => {
@@ -83,21 +99,21 @@ const HomePage: React.FC = () => {
{
icon: <FileTextOutlined style={{ fontSize: 24, color: '#6366f1' }} />,
title: '爆款开头复刻',
description: '一键复刻热门视频开篇,快速替换自有商品素材',
description: '上传参考视频与产品图片,一键复刻爆款视频开',
action: '立即创作',
path: '/initial',
},
{
icon: <ScissorOutlined style={{ fontSize: 24, color: '#f97316' }} />,
title: '批量混剪',
description: '多素材批量自动剪辑,智能筛选高清优质镜头片段',
title: '拆镜复刻',
description: '一键拆解画面分镜,助力仿拍或创作',
action: '开始混剪',
path: '/removelens',
},
{
icon: <RobotOutlined style={{ fontSize: 24, color: '#10b981' }} />,
title: 'AI成片',
description: 'AI全自动快速出片,支持文案生成/上传参考素材制作',
description: '输入想法、剧本或上传参考,智能生成视频/图片',
action: '立即生成',
path: '/conversation',
},
@@ -715,8 +731,6 @@ const HomePage: React.FC = () => {
gap: 6,
fontSize: 12,
color: '#64748b',
}}>
{(() => {
// 显示所属模块,而非媒体类型
@@ -781,68 +795,176 @@ const HomePage: React.FC = () => {
</div>
</div>
<div style={{
{/* <div style={{
fontSize: 13, color: '#6366f1', cursor: 'pointer', fontWeight: 500,
display: 'flex', alignItems: 'center', gap: 2,
}}>
更多案例
<ArrowRightOutlined style={{ fontSize: 11 }} />
</div>
</div> */}
</div>
<div className="stagger-children" style={{ display: 'flex', gap: 14 }}>
{materialCases.map((caseUrl, index) => (
{/* Tab切换 */}
<div style={{ marginBottom: 20 }}>
<Tabs
activeKey={activeCaseTab}
onChange={(key) => {
setActiveCaseTab(key);
getHomeCaseButton(key).then((btnRes: any) => {
if (btnRes?.categories?.[0]?.assets) {
setCaseAssets(btnRes.categories[0].assets);
} else {
setCaseAssets([]);
}
});
}}
items={caseHeader.map((item: any) => ({
key: item.id,
label: item.name,
}))}
className="homepage-tabs"
/>
</div>
{/* ========== 素材案例列表(与近期作品一致) ========== */}
<div className="stagger-children" style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 16 }}>
{caseAssets.length === 0 ? (
<div style={{
gridColumn: '1 / -1',
padding: '60px 0',
textAlign: 'center',
color: '#94a3b8',
fontSize: 14,
}}>
<PictureOutlined style={{ fontSize: 36, color: '#cbd5e1', marginBottom: 8 }} />
<div></div>
</div>
) : caseAssets.map((asset: any, index: number) => (
<div
key={index}
key={asset.id || index}
className="project-card"
onClick={() => setPreviewAsset(asset)}
style={{
flex: 1,
aspectRatio: '16/9',
borderRadius: 12,
overflow: 'hidden',
cursor: 'pointer',
background: '#f1f5f9',
position: 'relative',
}}
onMouseEnter={(e) => {
const overlay = e.currentTarget.querySelector('.case-overlay') as HTMLElement | null;
if (overlay) overlay.style.opacity = '1';
}}
onMouseLeave={(e) => {
const overlay = e.currentTarget.querySelector('.case-overlay') as HTMLElement | null;
if (overlay) overlay.style.opacity = '0';
background: '#fff',
border: '1px solid #e2e8f0',
}}
>
<img
src={caseUrl}
alt={`素材案例 ${index + 1}`}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
hover
<div
className="case-overlay"
style={{
position: 'absolute',
inset: 0,
background: 'linear-gradient(180deg, transparent 40%, rgba(99,102,241,0.75) 100%)',
opacity: 0,
transition: 'opacity 0.3s',
display: 'flex',
alignItems: 'flex-end',
justifyContent: 'center',
padding: 14,
color: '#fff',
fontSize: 13,
fontWeight: 600,
letterSpacing: 0.5,
}}
>
{index + 1}
{/* 媒体区域 16:9 */}
<div style={{ position: 'relative', aspectRatio: '16/9', }}>
{asset.mediaType === 'video' ? (
<>
<video
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${asset.url}`}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
muted
playsInline
/>
<div style={{
position: 'absolute',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'rgba(0,0,0,0.2)',
}}>
<VideoCameraOutlined style={{ fontSize: 28, color: '#fff' }} />
</div>
</>
) : (
<img
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${asset.url}`}
alt={asset.title || `素材 ${index + 1}`}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
)}
</div>
{/* 底部信息栏 */}
<div style={{
padding: '10px 12px',
background: '#f8fafc',
}}>
<div style={{
fontSize: 12,
color: '#64748b',
textAlign: 'center',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}>
{asset.title || `素材 ${index + 1}`}
</div>
</div>
</div>
))}
</div>
</div>
{/* ========== 预览弹窗 ========== */}
<Modal
open={!!previewAsset}
onCancel={() => {
previewVideoRef.current?.pause();
setPreviewAsset(null);
}}
footer={null}
width={760}
centered
className="preview-modal"
bodyStyle={{
padding: 0,
background: '#fff',
borderRadius: 16,
overflow: 'hidden',
}}
>
{/* 固定比例容器 16:9 */}
<div style={{
width: '100%',
paddingTop: '56.25%',
position: 'relative',
// background: '#000',
}}>
<div style={{
position: 'absolute',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
{previewAsset?.mediaType === 'video' ? (
<video
ref={previewVideoRef}
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewAsset.url}`}
controls
autoPlay
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
/>
) : (
<img
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewAsset?.url}`}
alt={previewAsset?.title}
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
/>
)}
</div>
</div>
{/* 底部标题栏 */}
{previewAsset?.title && (
<div style={{
padding: '14px 20px',
fontSize: 14,
color: '#4b5563',
fontWeight: 500,
borderTop: '1px solid #f1f5f9',
textAlign: 'center',
}}>
{previewAsset.title}
</div>
)}
</Modal>
</div>
);
};
@@ -459,7 +459,8 @@ const GenerateConver: React.FC = () => {
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text'
backgroundClip: 'text',
textAlign: 'center',
}}>
</h2>
+2 -1
View File
@@ -125,7 +125,8 @@ const ProjectsPage: React.FC = () => {
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text'
backgroundClip: 'text',
textAlign: 'center',
}}>
</h2>
+2 -1
View File
@@ -186,7 +186,8 @@ export default function VideoFrameExtractor() {
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text'
backgroundClip: 'text',
textAlign: 'center',
}}>
AI视频拆镜工作台
</h2>