爆款开头复刻样式修改

This commit is contained in:
孙佳艺
2026-06-22 16:11:55 +08:00
parent 03e5b049b4
commit 58b1372232
9 changed files with 2337 additions and 1239 deletions
@@ -588,7 +588,7 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
)} )}
</div> </div>
<div style={{ marginTop: -54, padding: '0 8px 0' }}> <div style={{ marginTop: 0, padding: '0 8px 0' }}>
<Slider <Slider
range range
min={0} min={0}
@@ -616,7 +616,7 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
marginTop: 0, marginTop: 0,
// backgroundColor: '#fff', // backgroundColor: '#fff',
// border: '3px solid #6366f1', // border: '3px solid #6366f1',
boxShadow: '0 2px 8px rgba(99, 102, 241, 0.3)', // boxShadow: '0 2px 8px rgba(99, 102, 241, 0.3)',
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
}, },
}} }}
+4 -5
View File
@@ -830,7 +830,7 @@ const AIChatPage: React.FC = () => {
// ==================== 渲染 ==================== // ==================== 渲染 ====================
return ( return (
<Layout style={{ height: 'calc(100vh - 90px)', background: '#fafafa', overflow: 'auto' }}> <Layout style={{ height: 'calc(100vh - 90px)', overflow: 'auto' }}>
{/* 左侧边栏 - 对话列表(已隐藏,保留代码) */} {/* 左侧边栏 - 对话列表(已隐藏,保留代码) */}
{false && ( {false && (
<Sider <Sider
@@ -917,12 +917,12 @@ const AIChatPage: React.FC = () => {
)} )}
{/* 主内容区 */} {/* 主内容区 */}
<Layout style={{ flex: 1 }}> <Layout style={{ flex: 1, background: '#ffffffff' }}>
{/* 头部 - 显示对话标题和模型信息 */} {/* 头部 - 显示对话标题和模型信息 */}
<Header <Header
style={{ style={{
background: '#fff', background: '#fff',
padding: '0 24px', // padding: '0 24px',
borderBottom: '1px solid #f0f0f0', borderBottom: '1px solid #f0f0f0',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
@@ -945,10 +945,9 @@ const AIChatPage: React.FC = () => {
<Content <Content
style={{ style={{
margin: 0, margin: 0,
background: '#fafafa',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
padding: 24, // padding: 24,
overflow: 'hidden', overflow: 'hidden',
}} }}
> >
+95 -22
View File
@@ -1,5 +1,6 @@
import React, { useEffect, useState, useLayoutEffect, useRef, useCallback } from 'react'; import React, { useEffect, useState, useLayoutEffect, useRef, useCallback } from 'react';
import { Button, Empty, Input, Select, Space, Typography, Tag, message, Upload, Modal, Progress, Table } from 'antd'; import { Button, Empty, Input, Select, Space, Typography, Tag, message, Upload, Modal, Progress, Table, DatePicker } from 'antd';
import dayjs from 'dayjs';
import { import {
SearchOutlined, SearchOutlined,
FilterOutlined, FilterOutlined,
@@ -32,6 +33,7 @@ const GeneratedRecord: React.FC = () => {
const [previewVisible, setPreviewVisible] = useState(false); const [previewVisible, setPreviewVisible] = useState(false);
const [previewItem, setPreviewItem] = useState<any>(null); const [previewItem, setPreviewItem] = useState<any>(null);
const videoRef = React.createRef<HTMLVideoElement>(); const videoRef = React.createRef<HTMLVideoElement>();
const [selectedDate, setSelectedDate] = useState<string>('');
// 批量上传相关状态 // 批量上传相关状态
const [uploadModalVisible, setUploadModalVisible] = useState(false); const [uploadModalVisible, setUploadModalVisible] = useState(false);
@@ -664,7 +666,7 @@ const GeneratedRecord: React.FC = () => {
const res = await getOAuthList({ page, page_size: pageSize }); const res = await getOAuthList({ page, page_size: pageSize });
const data = res?.data || res; const data = res?.data || res;
console.log(res); console.log(res);
setOauthList(data|| []); setOauthList(data || []);
setOauthTotal(res.pagination.total || 0); setOauthTotal(res.pagination.total || 0);
} catch (error) { } catch (error) {
console.error('加载授权列表失败:', error); console.error('加载授权列表失败:', error);
@@ -771,6 +773,17 @@ const GeneratedRecord: React.FC = () => {
} }
}; };
// 日期选择器变化处理函数
const handleDateChange = (dateString: string) => {
console.log(dateString);
console.log('qweqwe');
setSelectedDate(dateString);
};
useEffect(() => { useEffect(() => {
setLoading(true); setLoading(true);
let parameters = ''; let parameters = '';
@@ -779,27 +792,70 @@ const GeneratedRecord: React.FC = () => {
} else { } else {
parameters = `?gen_type=${filterMedia}&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`; parameters = `?gen_type=${filterMedia}&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`;
} }
gethistory(parameters).then((res: any) => {
const data = Array.isArray(res) ? res : (res?.groups || []);
data.forEach(group => {
group.page = 1;
});
// 如果是第一页,替换数据;否则追加数据
if (Pagebreak.page === 1) {
setRecordList(data);
} else {
setRecordList(prev => [...prev, ...data]);
}
setTotalnumber(res?.totalDays || 0); if (selectedDate) {
}).catch((err) => { let parameters = ``;
if (Pagebreak.page === 1) {
setRecordList([]); if (filterType === 'project') {
parameters = `${selectedDate}?gen_type=${filterMedia}&history_source=generation_record&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`;
} else {
parameters = `${selectedDate}?gen_type=${filterMedia}&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`;
} }
}).finally(() => { gethistoryItems(parameters).then((res: any) => {
setLoading(false); const data = Array.isArray(res) ? res : (res?.items || []);
}); let recordList = [{
}, [filterType, filterMedia, Pagebreak.page]); generatedDate: res.generatedDate,
items: data,
total: res.total,
page: res.page,
}];
console.log('data', data);
if (recordList && recordList[0].items.length > 0) {
setRecordList(recordList);
}else{
setRecordList([]);
}
console.log('recordList', recordList);
}).catch((err) => {
}).finally(() => {
setLoading(false);
});
try {
} catch (err) {
} finally {
}
} else {
gethistory(parameters).then((res: any) => {
const data = Array.isArray(res) ? res : (res?.groups || []);
data.forEach(group => {
group.page = 1;
});
console.log('qweqweqwe', data);
// 如果是第一页,替换数据;否则追加数据
if (Pagebreak.page === 1) {
setRecordList(data);
} else {
setRecordList(prev => [...prev, ...data]);
}
setTotalnumber(res?.totalDays || 0);
}).catch((err) => {
if (Pagebreak.page === 1) {
setRecordList([]);
}
}).finally(() => {
setLoading(false);
});
}
}, [filterType, filterMedia, Pagebreak.page, selectedDate]);
useEffect(() => { useEffect(() => {
setPreTestLoading(true); setPreTestLoading(true);
@@ -878,7 +934,7 @@ const GeneratedRecord: React.FC = () => {
}, [filterType, filterMedia]); }, [filterType, filterMedia]);
return ( return (
<div> <div style={{ minHeight: 'calc(100vh - 90px)', background: '#ffffffff' , overflowY: 'auto'}} >
{/* Header */} {/* Header */}
{/* <div style={{ marginBottom: 20 }}> {/* <div style={{ marginBottom: 20 }}>
<Typography.Title level={3} style={{ margin: '0 0 4px', color: '#1a1a2e', fontWeight: 700 }}> <Typography.Title level={3} style={{ margin: '0 0 4px', color: '#1a1a2e', fontWeight: 700 }}>
@@ -1047,6 +1103,23 @@ const GeneratedRecord: React.FC = () => {
> >
</Button> </Button>
<DatePicker
picker="date"
value={selectedDate ? dayjs(selectedDate) : undefined}
onChange={(date, dateString) => handleDateChange(dateString || '')}
format="YYYY-MM-DD"
style={{ width: 160, borderRadius: 8, border: '1px solid #e2e8f0' }}
placeholder="选择日期"
/>
{selectedDate && (
<Button
type="text"
onClick={() => handleDateChange('')}
style={{ color: '#94a3b8', fontSize: 12 }}
>
</Button>
)}
</Space> </Space>
</div> </div>
+377 -236
View File
@@ -318,7 +318,7 @@ function InitialInfo() {
// 获取引擎 ID 和视频参数 // 获取引擎 ID 和视频参数
const engineId = countType; const engineId = countType;
const videoParams = { const videoParams = {
engine_id : countType, engine_id: countType,
duration: videoDuration, duration: videoDuration,
aspect_ratio: videoAspectRatio, aspect_ratio: videoAspectRatio,
resolution: videoResolution, resolution: videoResolution,
@@ -333,7 +333,7 @@ function InitialInfo() {
// 这里可以添加下一步的逻辑,比如调用接口等 // 这里可以添加下一步的逻辑,比如调用接口等
}; };
const createvideo = (stepId: number,engineId: string) => { const createvideo = (stepId: number, engineId: string) => {
let params = { let params = {
engine_id: engineId, engine_id: engineId,
} }
@@ -362,33 +362,91 @@ function InitialInfo() {
return ( return (
<React.Fragment> <React.Fragment>
<div style={{ minHeight: 'calc(100vh - 90px)', background: '#f8fafc' }}> <div
<div style={{ height: '100%' }}> className="initialinfo-container"
style={{
margin: '-24px -32px -32px',
<div style={{ height: '100%', display: 'flex', justifyContent: 'space-between', gap: '2%' }}> height: 'calc(100vh - 34px)',
<div style={{ width: '70%', background: '#fff', borderRadius: 12, overflowY: 'auto', maxHeight: 'calc(100vh - 90px)' }}> // background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 50%, #f0f9ff 100%)',
position: 'relative',
// padding: '20px 32px',
boxSizing: 'border-box',
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
}}
>
<div style={{ borderBottom: '1px solid #e2e8f0', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px', boxSizing: 'border-box' }}>
<Button <div style={{ flex: 1, display: 'flex', justifyContent: 'space-between', alignItems: 'stretch', gap: '2%', position: 'relative', zIndex: 1, minHeight: 0 }}>
type="text"
icon={<ArrowLeftOutlined />} <div
onClick={() => navigate(-1)} className="replication-preview"
style={{ color: '#64748b' }} style={{
> flex: 4,
background: 'rgba(255,255,255,0.85)',
</Button> backdropFilter: 'blur(20px)',
<h3 style={{ margin: 0 }}> borderRadius: 20,
overflow: 'hidden',
border: '1px solid rgba(99, 102, 241, 0.1)',
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
display: 'flex',
flexDirection: 'column',
minHeight: 0,
}}
>
<div style={{ borderBottom: '1px solid rgba(99, 102, 241, 0.08)', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px 24px', background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)', boxSizing: 'border-box' }}>
<Button
type="text"
icon={<ArrowLeftOutlined />}
onClick={() => navigate(-1)}
style={{
color: '#64748b',
borderRadius: 10,
fontSize: 13,
height: 32,
transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
}}
>
</Button>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} />
<h3 style={{ margin: 0, fontSize: 16, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
- -
</h3> </h3>
<Button type="primary" ghost style={{ borderRadius: 6, padding: '4px 12px', fontSize: 12, borderColor: '#6366f1', color: '#6366f1' }} onClick={() => setIsModalOpen(true)}> <div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
</Button>
</div> </div>
<Button
type="primary"
ghost
style={{
borderRadius: 10,
padding: '4px 16px',
fontSize: 13,
background: 'rgba(99, 102, 241, 0.1)',
borderColor: 'rgba(99, 102, 241, 0.2)',
color: '#6366f1',
}}
onClick={() => setIsModalOpen(true)}
>
</Button>
</div>
<div style={{ flex: 1, overflowY: 'auto' }}>
<div className="material_box"> <div className="material_box">
<div style={{ display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'flex-start' }}> <div style={{ display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'flex-start' }}>
<div style={{ flex: 1, minWidth: 250 }}> <div style={{ flex: 1, minWidth: 250 }}>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span> <span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<div className="medio_box" style={{ aspectRatio: '16/9', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}> <span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
</span>
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
{taskDetail?.material?.materialVideoUrl ? ( {taskDetail?.material?.materialVideoUrl ? (
<video <video
controls controls
@@ -396,17 +454,20 @@ function InitialInfo() {
style={{ width: '100%', height: '100%', objectFit: 'contain' }} style={{ width: '100%', height: '100%', objectFit: 'contain' }}
/> />
) : ( ) : (
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 12 }}></div> <div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 13 }}></div>
)} )}
</div> </div>
</div> </div>
<div className="product_img" style={{ flex: 1, minWidth: 250 }}> <div className="product_img" style={{ flex: 1, minWidth: 250 }}>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span> <span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<div className="medio_box" style={{ aspectRatio: '1/1', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}> <span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
</span>
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
{taskDetail?.material?.materialImageUrl ? ( {taskDetail?.material?.materialImageUrl ? (
<img src={taskDetail.material.materialImageUrl} alt="" style={{ width: '100%', height: '100%', objectFit: 'contain' }} /> <img src={taskDetail.material.materialImageUrl} alt="" style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
) : ( ) : (
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 12 }}></div> <div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 13 }}></div>
)} )}
</div> </div>
</div> </div>
@@ -414,8 +475,11 @@ function InitialInfo() {
{taskDetail?.finalImageUrl && ( {taskDetail?.finalImageUrl && (
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 16 }}>
<div> <div>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span> <span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<div className="medio_box" style={{ aspectRatio: '1/1', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}> <span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
</span>
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
<img <img
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`} src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
alt="" alt=""
@@ -428,8 +492,11 @@ function InitialInfo() {
{taskDetail?.finalVideoUrl && ( {taskDetail?.finalVideoUrl && (
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 16 }}>
<div> <div>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span> <span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<div className="medio_box" style={{ aspectRatio: '16/9', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}> <span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
</span>
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
<video <video
controls controls
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`} src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
@@ -441,9 +508,30 @@ function InitialInfo() {
)} )}
</div> </div>
</div> </div>
<div style={{ width: '28%', minWidth: '400px', background: '#fff', borderRadius: 12, overflowY: 'auto', maxHeight: 'calc(100vh - 90px)' }}> </div>
<div style={{ padding: '14px' }}> </div> <div
className="replication-form"
style={{
flex: 1,
minWidth: '400px',
background: 'rgba(255,255,255,0.85)',
backdropFilter: 'blur(20px)',
borderRadius: 20,
overflow: 'hidden',
border: '1px solid rgba(99, 102, 241, 0.1)',
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
display: 'flex',
flexDirection: 'column',
minHeight: 0,
}}
>
<div style={{ padding: '16px 24px', borderBottom: '1px solid rgba(99, 102, 241, 0.08)', display: 'flex', alignItems: 'center', gap: 10, background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)' }}>
<div style={{ width: 4, height: 18, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
<span style={{ fontSize: 15, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}></span>
</div>
<div style={{ flex: 1, overflowY: 'auto', padding: '8px 16px 16px' }}>
<Collapse <Collapse
defaultActiveKey={['']} defaultActiveKey={['']}
ghost ghost
@@ -484,14 +572,14 @@ function InitialInfo() {
</div> </div>
), ),
children: ( children: (
<div style={{ padding: '16px', background: '#f8fafc', borderRadius: 8, marginTop: 8 }}> <div style={{ padding: '16px', background: 'linear-gradient(135deg, rgba(248,250,252,0.6) 0%, rgba(238,242,255,0.4) 100%)', borderRadius: 12, marginTop: 8, border: '1px solid rgba(99, 102, 241, 0.06)' }}>
{/* 步骤1: 原始素材 */} {/* 步骤1: 原始素材 */}
{step.childId === 1 && ( {step.childId === 1 && (
<> <>
<div style={{ display: 'flex', gap: 20, marginBottom: 20, alignItems: 'flex-start', flexWrap: 'wrap' }}> <div style={{ display: 'flex', gap: 20, marginBottom: 20, alignItems: 'flex-start', flexWrap: 'wrap' }}>
<div style={{ flex: 1, minWidth: 200 }}> <div style={{ flex: 1, minWidth: 200 }}>
<Text style={{ color: '#64748b', fontSize: 13, marginBottom: 10, display: 'block', fontWeight: 500 }}></Text> <Text style={{ color: '#475569', fontSize: 13, marginBottom: 10, display: 'block', fontWeight: 500 }}></Text>
<div style={{ height: 180, borderRadius: 10, background: '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <div style={{ height: 180, borderRadius: 12, background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', display: 'flex', alignItems: 'center', justifyContent: 'center', border: '1px solid rgba(99, 102, 241, 0.08)', boxShadow: '0 2px 8px rgba(99, 102, 241, 0.04)' }}>
{taskDetail?.material?.materialVideoUrl ? ( {taskDetail?.material?.materialVideoUrl ? (
<video <video
src={taskDetail.material.materialVideoUrl} src={taskDetail.material.materialVideoUrl}
@@ -501,8 +589,8 @@ function InitialInfo() {
) : ( ) : (
<div style={{ textAlign: 'center', color: '#94a3b8' }}> <div style={{ textAlign: 'center', color: '#94a3b8' }}>
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" style={{ marginBottom: 8 }}> <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" style={{ marginBottom: 8 }}>
<rect x="2" y="2" width="20" height="16" rx="2" ry="2"/> <rect x="2" y="2" width="20" height="16" rx="2" ry="2" />
<path d="M8 12l4 2 4-2"/> <path d="M8 12l4 2 4-2" />
</svg> </svg>
<div style={{ fontSize: 13 }}></div> <div style={{ fontSize: 13 }}></div>
</div> </div>
@@ -521,9 +609,9 @@ function InitialInfo() {
) : ( ) : (
<div style={{ textAlign: 'center', color: '#94a3b8' }}> <div style={{ textAlign: 'center', color: '#94a3b8' }}>
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" style={{ marginBottom: 8 }}> <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" style={{ marginBottom: 8 }}>
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/> <rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
<circle cx="8.5" cy="8.5" r="1.5" fill="currentColor"/> <circle cx="8.5" cy="8.5" r="1.5" fill="currentColor" />
<path d="M21 15l-5-5L5 21"/> <path d="M21 15l-5-5L5 21" />
</svg> </svg>
<div style={{ fontSize: 13 }}></div> <div style={{ fontSize: 13 }}></div>
</div> </div>
@@ -537,24 +625,27 @@ function InitialInfo() {
{/* 步骤2: 生成提示词 */} {/* 步骤2: 生成提示词 */}
{step.childId === 2 && ( {step.childId === 2 && (
<> <>
{/* <Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}> <div style={{ padding: '12px 14px', background: 'rgba(255,255,255,0.6)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.08)', marginBottom: 4 }}>
{taskDetail?.generationPrompt || '暂无提示词'} <Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
</Text> */} {step?.output?.payload?.prompt || '暂无提示词'}
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}> </Text>
{/* {taskDetail?.generationPrompt || '暂无提示词'} */} </div>
{step?.output?.payload?.prompt || '暂无提示词'}
</Text>
<Space style={{ marginTop: 16, gap: 12, width: '100%' }}> <Space style={{ marginTop: 16, gap: 12, width: '100%' }}>
<Button <Button
type="default" type="default"
icon={<EditOutlined />} icon={<EditOutlined />}
onClick={() => handleOpenModal(step?.output?.payload?.prompt, 'image', step.id)} onClick={() => handleOpenModal(step?.output?.payload?.prompt, 'image', step.id)}
style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }} style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }}
disabled={step.status !== 'completed'} disabled={step.status !== 'completed'}
> >
</Button> </Button>
<Button onClick={() => { message.info('正在生成图片,请稍候...'); createimage(step.id); }} type="primary" style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }} disabled={step.status !== 'completed'}> <Button
onClick={() => { message.info('正在生成图片,请稍候...'); createimage(step.id); }}
type="primary"
style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
disabled={step.status !== 'completed'}
>
</Button> </Button>
</Space> </Space>
@@ -563,19 +654,39 @@ function InitialInfo() {
{/* 步骤3: 生成产品融合图 */} {/* 步骤3: 生成产品融合图 */}
{step.childId === 3 && ( {step.childId === 3 && (
<> <>
<div style={{ borderRadius: 8, overflow: 'hidden', marginBottom: 16, background: '#f1f5f9' }}> <div style={{ borderRadius: 12, overflow: 'hidden', marginBottom: 16, background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', border: '1px solid rgba(99, 102, 241, 0.08)', boxShadow: '0 2px 8px rgba(99, 102, 241, 0.04)', position: 'relative' }}>
{/* {step.output.result.result_image_url} */}
{taskDetail.finalImageUrl ? ( {taskDetail.finalImageUrl ? (
<img src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`} alt="融合图" style={{ width: '100%', height: 180, objectFit: 'cover' }} /> <img src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`} alt="融合图" style={{ width: '100%', height: 180, objectFit: 'cover' }} />
) : ( ) : (
<div style={{ width: '100%', height: 180, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 14 }}></div> <div style={{ width: '100%', height: 180, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 14 }}></div>
)} )}
<Button
type="default"
icon={<EditOutlined />}
onClick={() => newcreateimage()}
disabled={step.status !== 'completed'}
style={{
position: 'absolute',
bottom: 8,
left: 8,
borderRadius: 8,
borderColor: 'rgba(99, 102, 241, 0.3)',
color: '#6366f1',
height: 30,
fontWeight: 500,
background: 'rgba(255,255,255,0.85)',
backdropFilter: 'blur(4px)',
padding: '0 12px',
fontSize: 12,
}}
>
</Button>
</div> </div>
{/* 引擎选择器 */} {/* 引擎选择器和视频参数设置 */}
<div style={{ marginBottom: 12 }}> <p style={{marginBottom:6,fontSize: 14, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}></p>
<div style={{ position: 'relative', display: 'inline-block' }}> <div style={{ width:'100%', display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
<button <button
onClick={() => { onClick={() => {
setShowEngineModal(!showEngineModal); setShowEngineModal(!showEngineModal);
@@ -583,12 +694,12 @@ function InitialInfo() {
}} }}
className="image-settings-trigger" className="image-settings-trigger"
style={{ style={{
minWidth: 200, width: '100%',
padding: '4px 12px', padding: '4px 12px',
height: 34, height: 34,
borderRadius: 8, borderRadius: 10,
border: 'none', border: '1px solid rgba(99, 102, 241, 0.15)',
backgroundColor: '#f1f5f9', backgroundColor: 'rgba(255,255,255,0.7)',
cursor: 'pointer', cursor: 'pointer',
display: 'inline-flex', display: 'inline-flex',
alignItems: 'center', alignItems: 'center',
@@ -596,7 +707,7 @@ function InitialInfo() {
transition: 'all 0.2s', transition: 'all 0.2s',
}} }}
> >
<SettingOutlined style={{ fontSize: 14, color: '#64748b' }} /> <SettingOutlined style={{ fontSize: 14, color: '#6366f1' }} />
<Text style={{ <Text style={{
fontSize: 14, fontSize: 14,
fontWeight: 500, fontWeight: 500,
@@ -612,7 +723,7 @@ function InitialInfo() {
style={{ style={{
position: 'absolute', position: 'absolute',
bottom: 'calc(100% + 8px)', bottom: 'calc(100% + 8px)',
left: -20, left: -10,
width: 350, width: 350,
backgroundColor: '#fff', backgroundColor: '#fff',
borderRadius: 16, borderRadius: 16,
@@ -702,11 +813,9 @@ function InitialInfo() {
</div> </div>
)} )}
</div> </div>
</div>
{/* 视频参数设置 */} {/* 视频参数设置 */}
<div style={{ marginBottom: 16 }}> <div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
<div style={{ position: 'relative', display: 'inline-block' }}>
<button <button
onClick={() => { onClick={() => {
setShowVideoSettingsModal(!showVideoSettingsModal); setShowVideoSettingsModal(!showVideoSettingsModal);
@@ -714,12 +823,12 @@ function InitialInfo() {
}} }}
className="image-settings-trigger" className="image-settings-trigger"
style={{ style={{
minWidth: 220, width: '100%',
padding: '4px 12px', padding: '4px 12px',
height: 34, height: 34,
borderRadius: 8, borderRadius: 10,
border: 'none', border: '1px solid rgba(99, 102, 241, 0.15)',
backgroundColor: '#f1f5f9', backgroundColor: 'rgba(255,255,255,0.7)',
cursor: 'pointer', cursor: 'pointer',
display: 'inline-flex', display: 'inline-flex',
alignItems: 'center', alignItems: 'center',
@@ -727,7 +836,7 @@ function InitialInfo() {
transition: 'all 0.2s', transition: 'all 0.2s',
}} }}
> >
<LayoutOutlined style={{ fontSize: 14, color: '#64748b' }} /> <LayoutOutlined style={{ fontSize: 14, color: '#6366f1' }} />
<Text style={{ <Text style={{
fontSize: 14, fontSize: 14,
fontWeight: 500, fontWeight: 500,
@@ -743,7 +852,7 @@ function InitialInfo() {
style={{ style={{
position: 'absolute', position: 'absolute',
bottom: 'calc(100% + 8px)', bottom: 'calc(100% + 8px)',
left: -20, left: -160,
width: 350, width: 350,
backgroundColor: '#fff', backgroundColor: '#fff',
borderRadius: 16, borderRadius: 16,
@@ -935,41 +1044,40 @@ function InitialInfo() {
)} )}
</div> </div>
</div> </div>
<Space style={{ width: '100%', gap: 12 }}> <Button
<Button type="default" icon={<EditOutlined />} style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 } type="primary"
} onClick={() => newcreateimage()} disabled={step.status !== 'completed'}> style={{ width: '100%', borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
onClick={() => { message.info('正在生成视频提示词,请稍候...'); handleNextStep(step.id); }}
</Button> disabled={step.status !== 'completed'}
<Button >
type="primary"
style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }} </Button>
onClick={() => { message.info('正在生成视频提示词,请稍候...'); handleNextStep(step.id); }}
disabled={step.status !== 'completed'}
>
</Button>
</Space>
</> </>
)} )}
{/* 步骤4: 生成视频提示词 */} {/* 步骤4: 生成视频提示词 */}
{step.childId === 4 && ( {step.childId === 4 && (
<> <>
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}> <div style={{ padding: '12px 14px', background: 'rgba(255,255,255,0.6)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.08)', marginBottom: 4 }}>
{/* {taskDetail?.videoPrompt || '暂无视频提示词'} */} <Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
{step?.output?.payload?.finalPrompt || '暂无提示词'} {step?.output?.payload?.finalPrompt || '暂无提示词'}
</Text>
</Text> </div>
<Space style={{ marginTop: 16, gap: 12, width: '100%' }}> <Space style={{ marginTop: 16, gap: 12, width: '100%' }}>
<Button <Button
type="default" type="default"
icon={<EditOutlined />} icon={<EditOutlined />}
onClick={() => handleOpenModal(step?.output?.payload?.promptSchema, 'video', step.id)} onClick={() => handleOpenModal(step?.output?.payload?.promptSchema, 'video', step.id)}
style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }} style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }}
disabled={step.status !== 'completed'} disabled={step.status !== 'completed'}
> >
</Button> </Button>
<Button onClick={() => { message.info('正在生成视频,请稍候...'); createvideo(step.id,step.engineId); }} type="primary" style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }} disabled={step.status !== 'completed'}> <Button
onClick={() => { message.info('正在生成视频,请稍候...'); createvideo(step.id, step.engineId); }}
type="primary"
style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
disabled={step.status !== 'completed'}
>
</Button> </Button>
</Space> </Space>
@@ -978,7 +1086,7 @@ function InitialInfo() {
{/* 步骤5: 生成最终视频 */} {/* 步骤5: 生成最终视频 */}
{step.childId === 5 && ( {step.childId === 5 && (
<> <>
<div style={{ borderRadius: 8, overflow: 'hidden', marginBottom: 16, position: 'relative', background: '#f1f5f9' }}> <div style={{ borderRadius: 12, overflow: 'hidden', marginBottom: 16, position: 'relative', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', border: '1px solid rgba(99, 102, 241, 0.08)', boxShadow: '0 2px 8px rgba(99, 102, 241, 0.04)' }}>
{step.status === 'completed' && taskDetail?.finalVideoUrl ? ( {step.status === 'completed' && taskDetail?.finalVideoUrl ? (
<video src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`} style={{ width: '100%', height: 180, objectFit: 'cover' }} controls /> <video src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`} style={{ width: '100%', height: 180, objectFit: 'cover' }} controls />
) : step.status === 'processing' ? ( ) : step.status === 'processing' ? (
@@ -993,13 +1101,19 @@ function InitialInfo() {
)} )}
</div> </div>
<Space style={{ width: '100%', gap: 12 }}> <Space style={{ width: '100%', gap: 12 }}>
<Button onClick={() => agincreatevideo()} type="default" icon={<EditOutlined />} style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }} disabled={step.status !== 'completed'}> <Button
onClick={() => agincreatevideo()}
type="default"
icon={<EditOutlined />}
style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }}
disabled={step.status !== 'completed'}
>
</Button> </Button>
<Button <Button
type="primary" type="primary"
icon={<DownloadOutlined />} icon={<DownloadOutlined />}
style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }} style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
disabled={step.status !== 'completed'} disabled={step.status !== 'completed'}
onClick={() => { onClick={() => {
const videoUrl = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail?.finalVideoUrl}&download=1`; const videoUrl = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail?.finalVideoUrl}&download=1`;
@@ -1025,21 +1139,33 @@ function InitialInfo() {
</div> </div>
</div> </div>
<Modal <Modal
title={currentType === 'video' ? '修改视频提示词' : '修改提示词'} title={
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 4, height: 18, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
<span style={{ fontSize: 15, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
{currentType === 'video' ? '修改视频提示词' : '修改提示词'}
</span>
</div>
}
open={modalVisible} open={modalVisible}
onCancel={() => { if (!promptSaving) setModalVisible(false); }} onCancel={() => { if (!promptSaving) setModalVisible(false); }}
footer={[ footer={[
<Button key="cancel" onClick={() => setModalVisible(false)} disabled={promptSaving}></Button>, <Button key="cancel" onClick={() => setModalVisible(false)} disabled={promptSaving} style={{ borderRadius: 8 }}></Button>,
<Button key="confirm" type="primary" onClick={handleConfirm} loading={promptSaving} disabled={promptSaving}></Button>, <Button key="confirm" type="primary" onClick={handleConfirm} loading={promptSaving} disabled={promptSaving} style={{ borderRadius: 8, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none' }}></Button>,
]} ]}
width={800} width={800}
style={{ borderRadius: 16 }}
styles={{
header: { background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)', borderBottom: '1px solid rgba(99, 102, 241, 0.08)', padding: '16px 24px' },
body: { background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 50%, #f0f9ff 100%)', padding: '20px 24px' },
}}
> >
{currentType === 'image' ? ( {currentType === 'image' ? (
<TextArea <TextArea
value={promptText} value={promptText}
onChange={(e) => setPromptText(e.target.value)} onChange={(e) => setPromptText(e.target.value)}
rows={8} rows={8}
style={{ width: '100%', height: 300 }} style={{ width: '100%', height: 300, borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.15)', background: 'rgba(255,255,255,0.8)' }}
placeholder="请输入提示词" placeholder="请输入提示词"
/> />
) : ( ) : (
@@ -1050,171 +1176,186 @@ function InitialInfo() {
</Modal> </Modal>
{/* 创作记录弹窗 */} {/* 创作记录弹窗 */}
<Modal <Modal
title="创作记录" title={
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 4, height: 18, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
<span style={{ fontSize: 15, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
</span>
</div>
}
open={isModalOpen} open={isModalOpen}
onCancel={() => setIsModalOpen(false)} onCancel={() => setIsModalOpen(false)}
width={800} width={850}
footer={null} footer={null}
style={{ borderRadius: 12 }} style={{ borderRadius: 16 }}
styles={{
header: { background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)', borderBottom: '1px solid rgba(99, 102, 241, 0.08)', padding: '16px 24px' },
body: { background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 50%, #f0f9ff 100%)', },
}}
> >
{/* 搜索区域 */} {/* 搜索区域 */}
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 16 }}> <div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 16 }}>
<Input <Input
placeholder="搜索产品名称" placeholder="搜索产品名称"
style={{ width: 200, borderRadius: 6, marginRight: 8 }} style={{ width: 200, borderRadius: 8, marginRight: 8, border: '1px solid rgba(99, 102, 241, 0.15)', background: 'rgba(255,255,255,0.8)' }}
/> />
<Button type="primary" style={{ borderRadius: 6 }}> <Button type="primary" style={{ borderRadius: 8, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}>
</Button> </Button>
</div> </div>
{/* 表格 */} {/* 表格 */}
<Table <Table
columns={[ columns={[
{ {
title: '产品名称', title: '产品名称',
dataIndex: 'targetProjectName', dataIndex: 'targetProjectName',
key: 'targetProjectName', key: 'targetProjectName',
align: 'center', align: 'center',
}, },
{ {
title: '状态', title: '状态',
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
align: 'center', align: 'center',
render: (text: string, record: any) => { render: (text: string, record: any) => {
const status = text; const status = text;
const currentStepCode = record.current_step_code || record.currentStepCode; const currentStepCode = record.current_step_code || record.currentStepCode;
const getStatusText = () => { const getStatusText = () => {
if (currentStepCode === 'image_prompt_optimize') { if (currentStepCode === 'image_prompt_optimize') {
switch (status) { switch (status) {
case 'waiting_user': case 'waiting_user':
return '等待融合图生成'; return { text: '等待融合图生成', color: '#f59e0b' };
case 'processing': case 'processing':
return '图片提示词生成中'; return { text: '图片提示词生成中', color: '#f59e0b' };
case 'completed': case 'completed':
return '图片提示词生成成功'; return { text: '图片提示词生成成功', color: '#10b981' };
case 'failed': case 'failed':
return '图片提示词生成失败'; return { text: '图片提示词生成失败', color: '#ef4444' };
default: default:
return status || '-'; return { text: status || '-', color: '#666' };
} }
} else if (currentStepCode === 'image_generate') { } else if (currentStepCode === 'image_generate') {
switch (status) { switch (status) {
case 'waiting_user': case 'waiting_user':
return '等待生成视频提示词'; return { text: '等待生成视频提示词', color: '#f59e0b' };
case 'processing': case 'processing':
return '融合图生成中'; return { text: '融合图生成中', color: '#f59e0b' };
case 'completed': case 'completed':
return '融合图生成成功'; return { text: '融合图生成成功', color: '#10b981' };
case 'failed': case 'failed':
return '融合图生成失败'; return { text: '融合图生成失败', color: '#ef4444' };
default: default:
return status || '-'; return { text: status || '-', color: '#666' };
} }
} else if (currentStepCode === 'video_prompt_optimize') { } else if (currentStepCode === 'video_prompt_optimize') {
switch (status) { switch (status) {
case 'waiting_user': case 'waiting_user':
return '等待最终视频生成'; return { text: '等待最终视频生成', color: '#f59e0b' };
case 'processing': case 'processing':
return '视频提示词生成中'; return { text: '视频提示词生成中', color: '#f59e0b' };
case 'completed': case 'completed':
return '视频提示词生成成功'; return { text: '视频提示词生成成功', color: '#10b981' };
case 'failed': case 'failed':
return '视频提示词生成失败'; return { text: '视频提示词生成失败', color: '#ef4444' };
default: default:
return status || '-'; return { text: status || '-', color: '#666' };
} }
} else if (currentStepCode === 'video_generate') { } else if (currentStepCode === 'video_generate') {
switch (status) { switch (status) {
case 'waiting_user': case 'waiting_user':
return ''; return { text: '', color: '#666' };
case 'processing': case 'processing':
return '最终视频生成中'; return { text: '最终视频生成中', color: '#f59e0b' };
case 'completed': case 'completed':
return '最终视频生成成功'; return { text: '任务完成', color: '#10b981' };
case 'failed': case 'failed':
return '最终视频生成失败'; return { text: '最终视频生成失败', color: '#ef4444' };
default: default:
return status || '-'; return { text: status || '-', color: '#666' };
} }
} else if (currentStepCode === 'material_input') { } else if (currentStepCode === 'material_input') {
switch (status) { switch (status) {
case 'waiting_user': case 'waiting_user':
return '等待生成图片提示词'; return { text: '等待生成图片提示词', color: '#f59e0b' };
case 'processing': case 'processing':
return '素材处理中'; return { text: '素材处理中', color: '#f59e0b' };
case 'completed': case 'completed':
return '素材上传成功'; return { text: '素材上传成功', color: '#10b981' };
case 'failed': case 'failed':
return '素材上传失败'; return { text: '素材上传失败', color: '#ef4444' };
default: default:
return status || '-'; return { text: status || '-', color: '#666' };
} }
} else { } else {
const statusMap: Record<string, string> = { const statusMap: Record<string, { text: string; color: string }> = {
'pending': '子任务待处理', 'pending': { text: '子任务待处理', color: '#f59e0b' },
'waiting_user': '等待用户确认或触发', 'waiting_user': { text: '等待用户确认或触发', color: '#f59e0b' },
'processing': '子任务处理中', 'processing': { text: '子任务处理中', color: '#f59e0b' },
'completed': '子任务完成', 'completed': { text: '子任务完成', color: '#10b981' },
'failed': '子任务失败', 'failed': { text: '子任务失败', color: '#ef4444' },
'cancelled': '子任务取消', 'cancelled': { text: '子任务取消', color: '#94a3b8' },
}; };
return statusMap[status] || status || '-'; return statusMap[status] || { text: status || '-', color: '#666' };
} }
}; };
return getStatusText(); const result = getStatusText() as { text: string; color: string };
}, if (typeof result === 'string') {
}, return <span style={{ fontSize: 12, color: '#666', fontWeight: 500 }}>{result}</span>;
{ }
title: '创建时间', return <span style={{ fontSize: 12, color: result.color, fontWeight: 500 }}>{result.text}</span>;
dataIndex: 'createdAt', },
key: 'createdAt', },
align: 'center', {
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
align: 'center',
render: (text: string) => { render: (text: string) => {
if (!text) return '-'; if (!text) return '-';
const date = new Date(text); const date = new Date(text);
const year = date.getFullYear(); const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}, },
}, },
{ {
title: '操作', title: '操作',
key: 'action', key: 'action',
align: 'center', align: 'center',
render: (_, record) => ( render: (_, record) => (
<button <button
onClick={() => navigate(`/initial/${record.id}/initialinfo`)} onClick={() => navigate(`/initial/${record.id}/initialinfo`)}
style={{ color: '#6366f1', textDecoration: 'none', fontSize: 12, border: 'none', background: 'none', cursor: 'pointer' }} style={{ color: '#6366f1', textDecoration: 'none', fontSize: 12, border: 'none', background: 'rgba(99, 102, 241, 0.08)', padding: '4px 12px', borderRadius: 8, cursor: 'pointer', transition: 'all 0.2s', fontWeight: 500 }}
> >
</button> </button>
), ),
}, },
]} ]}
dataSource={tableData} dataSource={tableData}
rowKey="id" rowKey="id"
pagination={{ pagination={{
current: currentPage, current: currentPage,
pageSize: pageSize, pageSize: pageSize,
total: total, total: total,
showSizeChanger: true, showSizeChanger: true,
showQuickJumper: true, showQuickJumper: true,
showTotal: (total) => `${total}`, showTotal: (total) => `${total}`,
onChange: handlePageChange, onChange: handlePageChange,
}} }}
style={{ fontSize: 13 }} style={{ fontSize: 13 }}
scroll={{ y: 350 }} scroll={{ y: 350 }}
/> />
</Modal> </Modal>
</React.Fragment> </React.Fragment>
); );
File diff suppressed because it is too large Load Diff
+467 -195
View File
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { Button, Drawer, Input, Popconfirm, Spin, Table, Tag, Tooltip, Upload, message } from 'antd'; import { Button, Drawer, Input, Modal, Popconfirm, Spin, Table, Tag, Tooltip, Upload, message } from 'antd';
import { ArrowLeftOutlined, PlusOutlined, XOutlined } from '@ant-design/icons'; import { ArrowLeftOutlined, PlusOutlined, XOutlined } from '@ant-design/icons';
import { createRemoveLens, getShotReplicationDetail, Removelist, removeCreate, splitCustom, uploadImage } from '../api'; import { createRemoveLens, getShotReplicationDetail, Removelist, removeCreate, splitCustom, uploadImage } from '../api';
import VideoTrimPicker from '../components/VideoTrimPicker'; import VideoTrimPicker from '../components/VideoTrimPicker';
@@ -23,6 +23,10 @@ function RemoveInfo() {
const [drawerVisible, setDrawerVisible] = useState(false); const [drawerVisible, setDrawerVisible] = useState(false);
const [trimModalVisible, setTrimModalVisible] = useState(false); const [trimModalVisible, setTrimModalVisible] = useState(false);
const [currentSegment, setCurrentSegment] = useState<string | null>(null); const [currentSegment, setCurrentSegment] = useState<string | null>(null);
const [currentSegmentName, setCurrentSegmentName] = useState<string | null>(null);
const [previewVideoUrl, setPreviewVideoUrl] = useState<string | null>(null);
const [previewModalVisible, setPreviewModalVisible] = useState(false);
const previewVideoRef = useRef<HTMLVideoElement>(null);
const [productName, setProductName] = useState(''); const [productName, setProductName] = useState('');
const [productSellingPoint, setProductSellingPoint] = useState(''); const [productSellingPoint, setProductSellingPoint] = useState('');
const [productImage, setProductImage] = useState(''); const [productImage, setProductImage] = useState('');
@@ -31,6 +35,7 @@ function RemoveInfo() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [splitLoading, setSplitLoading] = useState(false); const [splitLoading, setSplitLoading] = useState(false);
const [autoSplitLoading, setAutoSplitLoading] = useState(false); const [autoSplitLoading, setAutoSplitLoading] = useState(false);
const pollingIntervalRef = useRef<number | null>(null);
const videoUrl = useMemo(() => buildAssetUrl(taskDetail?.videoUrl), [taskDetail?.videoUrl]); const videoUrl = useMemo(() => buildAssetUrl(taskDetail?.videoUrl), [taskDetail?.videoUrl]);
@@ -58,7 +63,69 @@ function RemoveInfo() {
if (!creatID) return; if (!creatID) return;
try { try {
const res = await Removelist(creatID); const res = await Removelist(creatID);
setTableData(res.items || []); const items = res.items || [];
setTableData(items);
// 检查是否需要开启轮询
const needsPolling = items.some(
(item: any) =>
item.moduleProjectCurrentStepCode !== 'video_generate' &&
item.moduleProjectStatus !== 'completed' &&
item.moduleProjectStatus !== 'failed'
);
if (needsPolling) {
// 如果没有轮询在运行,则开启轮询
if (!pollingIntervalRef.current) {
pollingIntervalRef.current = window.setInterval(async () => {
try {
const pollRes = await Removelist(creatID);
const pollItems = pollRes.items || [];
// 只更新未完成的项目
setTableData((prevData) =>
prevData.map((prevItem) => {
const pollItem = pollItems.find((p: any) => p.id === prevItem.id);
if (pollItem) {
// 只有当原项目未完成时才更新
if (
prevItem.moduleProjectCurrentStepCode !== 'video_generate' ||
(prevItem.moduleProjectStatus !== 'completed' && prevItem.moduleProjectStatus !== 'failed')
) {
return pollItem;
}
}
return prevItem;
})
);
// 检查是否所有项目都已完成
const allCompleted = pollItems.every(
(item: any) =>
item.moduleProjectCurrentStepCode === 'video_generate' ||
item.moduleProjectStatus === 'completed' ||
item.moduleProjectStatus === 'failed'
);
if (allCompleted) {
// 停止轮询
if (pollingIntervalRef.current) {
clearInterval(pollingIntervalRef.current);
pollingIntervalRef.current = null;
}
}
} catch (error) {
console.error('轮询获取拆镜列表失败:', error);
}
}, 30000); // 30秒轮询
}
} else {
// 如果不需要轮询,停止现有轮询
if (pollingIntervalRef.current) {
clearInterval(pollingIntervalRef.current);
pollingIntervalRef.current = null;
}
}
} catch { } catch {
message.error('获取拆镜列表失败'); message.error('获取拆镜列表失败');
} }
@@ -70,10 +137,19 @@ function RemoveInfo() {
useEffect(() => { useEffect(() => {
refreshPageData(); refreshPageData();
// 组件卸载时清除轮询
return () => {
if (pollingIntervalRef.current) {
clearInterval(pollingIntervalRef.current);
pollingIntervalRef.current = null;
}
};
}, [refreshPageData]); }, [refreshPageData]);
const handleGenerate = (segmentId: string) => { const handleGenerate = (segmentId: string, segmentName: string) => {
setCurrentSegment(segmentId); setCurrentSegment(segmentId);
setCurrentSegmentName(segmentName);
setDrawerVisible(true); setDrawerVisible(true);
}; };
@@ -81,6 +157,7 @@ function RemoveInfo() {
setDrawerVisible(false); setDrawerVisible(false);
setCurrentSegment(null); setCurrentSegment(null);
setProductName(''); setProductName('');
setCurrentSegmentName('');
setProductSellingPoint(''); setProductSellingPoint('');
setProductImage(''); setProductImage('');
}; };
@@ -218,13 +295,26 @@ function RemoveInfo() {
width: 150, width: 150,
align: 'center' as const, align: 'center' as const,
render: (_: any, record: any) => ( render: (_: any, record: any) => (
<div style={{ position: 'relative', width: 120, height: 80, borderRadius: 6, overflow: 'hidden', background: '#f1f5f9' }}> <div style={{ position: 'relative', width: 120, height: 80, borderRadius: 6, overflow: 'hidden', background: '#f1f5f9', cursor: 'pointer' }}
onClick={() => {
console.log('video clicked:', record.segmentVideoUrl);
if (record.segmentVideoUrl) {
setPreviewVideoUrl(buildAssetUrl(record.segmentVideoUrl));
setPreviewModalVisible(true);
}
}}>
{record.segmentVideoUrl ? ( {record.segmentVideoUrl ? (
<video <>
controls <video
src={buildAssetUrl(record.segmentVideoUrl)} src={buildAssetUrl(record.segmentVideoUrl)}
style={{ width: '100%', height: '100%', objectFit: 'cover' }} style={{ width: '100%', height: '100%', objectFit: 'cover', pointerEvents: 'none' }}
/> />
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,0.3)' }}>
<div style={{ width: 32, height: 32, borderRadius: '50%', background: 'rgba(255,255,255,0.9)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{ width: 0, height: 0, borderTop: '8px solid transparent', borderBottom: '8px solid transparent', borderLeft: '14px solid #6366f1', marginLeft: 2 }} />
</div>
</div>
</>
) : ( ) : (
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 12 }}> <div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 12 }}>
{record.splitStatus === 'failed' ? '切割失败' : '切割中'} {record.splitStatus === 'failed' ? '切割失败' : '切割中'}
@@ -238,15 +328,7 @@ function RemoveInfo() {
width: 500, width: 500,
align: 'left' as const, align: 'left' as const,
render: (_: any, record: any) => { render: (_: any, record: any) => {
console.log('视频内容列数据:', {
record,
sourceMode: record.source_mode,
sourceMode2: record.sourceMode,
analysisStatus: record.analysis_status,
analysisStatus2: record.analysisStatus,
segmentContent: record.segmentContent,
lastError: record.lastError,
});
// AI建议的片段直接显示内容,不经过分析状态判断 // AI建议的片段直接显示内容,不经过分析状态判断
if (record.source_mode === 'ai_suggestion' || record.sourceMode === 'ai_suggestion') { if (record.source_mode === 'ai_suggestion' || record.sourceMode === 'ai_suggestion') {
@@ -296,91 +378,95 @@ function RemoveInfo() {
render: (text: string, record: any) => { render: (text: string, record: any) => {
const status = text; const status = text;
const currentStepCode = record.moduleProjectCurrentStepCode || record.currentStepCode; const currentStepCode = record.moduleProjectCurrentStepCode || record.currentStepCode;
console.log('currentStepCode',status, currentStepCode);
const getStatusText = () => { const getStatusText = () => {
if (currentStepCode === 'image_prompt_optimize') { if (currentStepCode === 'image_prompt_optimize') {
switch (status) { switch (status) {
case 'waiting_user': case 'waiting_user':
return '等待融合图生成'; return { text: '等待融合图生成', color: '#f59e0b' };
case 'processing': case 'processing':
return '图片提示词生成中'; return { text: '图片提示词生成中', color: '#f59e0b' };
case 'completed': case 'completed':
return '图片提示词生成成功'; return { text: '图片提示词生成成功', color: '#10b981' };
case 'failed': case 'failed':
return '图片提示词生成失败'; return { text: '图片提示词生成失败', color: '#ef4444' };
default: default:
return status || '-'; return { text: status || '-', color: '#666' };
} }
} else if (currentStepCode === 'image_generate') { } else if (currentStepCode === 'image_generate') {
switch (status) { switch (status) {
case 'waiting_user': case 'waiting_user':
return '等待生成视频提示词'; return { text: '等待生成视频提示词', color: '#f59e0b' };
case 'processing': case 'processing':
return '融合图生成中'; return { text: '融合图生成中', color: '#f59e0b' };
case 'completed': case 'completed':
return '融合图生成成功'; return { text: '融合图生成成功', color: '#10b981' };
case 'failed': case 'failed':
return '融合图生成失败'; return { text: '融合图生成失败', color: '#ef4444' };
default: default:
return status || '-'; return status || '-';
} }
} else if (currentStepCode === 'video_prompt_optimize') { } else if (currentStepCode === 'video_prompt_optimize') {
switch (status) { switch (status) {
case 'waiting_user': case 'waiting_user':
return '等待最终视频生成'; return { text: '等待最终视频生成', color: '#f59e0b' };
case 'processing': case 'processing':
return '视频提示词生成中'; return { text: '视频提示词生成中', color: '#f59e0b' };
case 'completed': case 'completed':
return '视频提示词生成成功'; return { text: '视频提示词生成成功', color: '#10b981' };
case 'failed': case 'failed':
return '视频提示词生成失败'; return { text: '视频提示词生成失败', color: '#ef4444' };
default: default:
return status || '-'; return { text: status || '-', color: '#666' };
} }
} else if (currentStepCode === 'video_generate') { } else if (currentStepCode === 'video_generate') {
switch (status) { switch (status) {
case 'waiting_user': case 'waiting_user':
return ''; return { text: '', color: '#666' };
case 'processing': case 'processing':
return '最终视频生成中'; return { text: '最终视频生成中', color: '#f59e0b' };
case 'completed': case 'completed':
return '最终视频生成成功'; return { text: '任务完成', color: '#10b981' };
case 'failed': case 'failed':
return '最终视频生成失败'; return { text: '最终视频生成失败', color: '#ef4444' };
default: default:
return status || '-'; return { text: status || '-', color: '#666' };
} }
} else if (currentStepCode === 'material_input') { } else if (currentStepCode === 'material_input') {
switch (status) { switch (status) {
case 'waiting_user': case 'waiting_user':
return '等待生成图片提示词'; return { text: '等待生成图片提示词', color: '#f59e0b' };
case 'processing': case 'processing':
return '素材处理中'; return { text: '素材处理中', color: '#f59e0b' };
case 'completed': case 'completed':
return '素材上传成功'; return { text: '素材上传成功', color: '#10b981' };
case 'failed': case 'failed':
return '素材上传失败'; return { text: '素材上传失败', color: '#ef4444' };
default: default:
return status || '-'; return { text: status || '-', color: '#666' };
} }
} else { } else {
if (!record.moduleProjectId) { if (!record.moduleProjectId) {
return '待生成任务'; return { text: '待生成任务', color: '#94a3b8' };
} }
const statusMap: Record<string, string> = { const statusMap: Record<string, { text: string; color: string }> = {
'pending': '子任务待处理', 'pending': { text: '子任务待处理', color: '#f59e0b' },
'waiting_user': '等待用户确认或触发', 'waiting_user': { text: '等待用户确认或触发', color: '#f59e0b' },
'processing': '子任务处理中', 'processing': { text: '子任务处理中', color: '#f59e0b' },
'completed': '子任务完成', 'completed': { text: '子任务完成', color: '#10b981' },
'failed': '子任务失败', 'failed': { text: '子任务失败', color: '#ef4444' },
'cancelled': '子任务取消', 'cancelled': { text: '子任务取消', color: '#94a3b8' },
}; };
return statusMap[status] || status || '-'; const result = statusMap[status] || { text: status || '-', color: '#666' };
return result;
} }
}; };
return getStatusText(); const result = getStatusText() as { text: string; color: string };
if (typeof result === 'string') {
return <span style={{ fontSize: 12, color: '#666', fontWeight: 500 }}>{result}</span>;
}
return <span style={{ fontSize: 12, color: result.color, fontWeight: 500 }}>{result.text}</span>;
}, },
@@ -407,16 +493,16 @@ function RemoveInfo() {
<Button <Button
type="text" type="text"
onClick={() => navigate(`/removelens/${record.moduleProjectId}/removefenbu`)} onClick={() => navigate(`/removelens/${record.moduleProjectId}/removefenbu`)}
style={{ color: '#656efa', fontSize: 12, padding: 0, display: 'flex', alignItems: 'center', gap: 4 }} style={{ color: '#10b981', fontSize: 12, padding: 0, display: 'flex', alignItems: 'center', gap: 4 }}
> >
</Button> </Button>
) : ( ) : (
<Button <Button
type="text" type="text"
onClick={() => handleGenerate(String(record.id))} onClick={() => handleGenerate(String(record.id), record.segmentName)}
disabled={!canCreateReplication(record)} disabled={!canCreateReplication(record)}
style={{ color: canCreateReplication(record) ? '#656efa' : '#94a3b8', fontSize: 12, padding: 0, display: 'flex', alignItems: 'center', gap: 4 }} style={{ color: canCreateReplication(record) ? '#f59e0b' : '#94a3b8', fontSize: 12, padding: 0, display: 'flex', alignItems: 'center', gap: 4 }}
> >
{canCreateReplication(record) ? '视频生成' : '待切割完成'} {canCreateReplication(record) ? '视频生成' : '待切割完成'}
</Button> </Button>
@@ -430,76 +516,124 @@ function RemoveInfo() {
<> <>
<div <div
style={{ style={{
minHeight: 'calc(100vh - 90px)', margin: '-24px -32px -32px',
borderRadius: 20,
minHeight: 'calc(100vh - 32px)',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
overflow: 'hidden', overflow: 'hidden',
background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 50%, #f0f9ff 100%)',
position: 'relative',
}} }}
> >
<div style={{ background: '#fff' }}> {/* 背景装饰 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}> <div style={{ position: 'absolute', top: -100, right: -100, width: 300, height: 300, background: 'radial-gradient(circle, rgba(99,102,241,0.08) 0%, transparent 70%)', borderRadius: '50%' }} />
<Button <div style={{ position: 'absolute', bottom: -100, left: -100, width: 300, height: 300, background: 'radial-gradient(circle, rgba(167,139,250,0.08) 0%, transparent 70%)', borderRadius: '50%' }} />
type="text"
icon={<ArrowLeftOutlined />} {/* 标题栏 */}
onClick={() => navigate(-1)} <div style={{ padding: '20px 32px', display: 'flex', alignItems: 'center', gap: 16, position: 'relative', zIndex: 10 }}>
style={{ fontSize: 16, color: '#666' }} <Button
/> type="text"
icon={<ArrowLeftOutlined />}
onClick={() => navigate(-1)}
style={{
fontSize: 16,
color: '#6366f1',
background: 'rgba(99, 102, 241, 0.1)',
borderRadius: 12,
padding: '8px 16px',
transition: 'all 0.2s ease',
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.15)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.1)';
}}
/>
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<div style={{ width: 40, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} />
<h1 style={{ fontSize: 22, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', margin: 0 }}>
AI视频拆镜工作台
</h1>
<div style={{ width: 40, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
</div>
</div> </div>
</div> </div>
{taskDetail ? ( {taskDetail ? (
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', overflow: 'hidden' }}> <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', overflow: 'hidden', padding: '0 32px 32px' }}>
<div style={{ flex: 1, background: '#fff', borderRadius: 12, display: 'flex', alignItems: 'center', gap: 30, }}> {/* 视频总结卡片 */}
{/* <div style={{height: '100%', display: 'flex', alignItems: 'center', gap: 30,}}> */} <div
<div style={{ position: 'relative', width: 280, height: 160, borderRadius: 8, overflow: 'hidden', flexShrink: 0 }}> style={{
<video flex: 1,
controls background: 'linear-gradient(135deg, rgba(255,255,255,0.8) 0%, rgba(255,255,255,0.6) 100%)',
src={videoUrl} backdropFilter: 'blur(20px)',
style={{ width: '100%', height: '100%', objectFit: 'cover' }} borderRadius: 20,
/> display: 'flex',
</div> alignItems: 'center',
gap: 30,
padding: 24,
border: '1px solid rgba(99, 102, 241, 0.1)',
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.8)',
position: 'relative',
overflow: 'hidden',
}}
>
{/* 卡片装饰 */}
<div style={{ position: 'absolute', top: -50, right: -50, width: 200, height: 200, background: 'radial-gradient(circle, rgba(99,102,241,0.05) 0%, transparent 70%)', borderRadius: '50%' }} />
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}> <div style={{ position: 'relative', width: 280, height: 160, borderRadius: 12, overflow: 'hidden', flexShrink: 0, boxShadow: '0 4px 16px rgba(0,0,0,0.1)' }}>
<div> <video
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> controls
<h2 style={{ fontSize: 20, fontWeight: 600, marginBottom: 16 }}></h2> src={videoUrl}
<div style={{ textAlign: 'right' }}> style={{ width: '100%', height: '100%', objectFit: 'cover' }}
<span style={{ color: '#999', fontSize: 12 }}>: {taskDetail.createdAt}</span> />
</div> </div>
</div>
<div style={{ display: 'flex', marginBottom: 12 }}> <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', position: 'relative', zIndex: 1 }}>
<span style={{ color: '#999', fontSize: 14, marginRight: 12, width: 80, flexShrink: 0 }}>:</span> <div>
<span style={{ color: '#333', fontWeight: 500 }}>{taskDetail.title}</span> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
</div> <h2 style={{ fontSize: 20, fontWeight: 600, marginBottom: 16, background: 'linear-gradient(135deg, #1e293b 0%, #334155 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}></h2>
<div style={{ display: 'flex', marginBottom: 12 }}> <div style={{ textAlign: 'right' }}>
<span style={{ color: '#999', fontSize: 14, marginRight: 12, width: 80, flexShrink: 0 }}>:</span> <span style={{ color: '#94a3b8', fontSize: 12 }}>: {taskDetail.createdAt ? new Date(taskDetail.createdAt).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }) : '-'}</span>
{taskDetail.analysisStatus === 'processing' ? (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<Spin size="small" tip="分析中" style={{ color: '#f59e0b' }} />
</div>
) : taskDetail.analysisStatus === 'completed' ? (
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
{(taskDetail.originalVideoAudience?.split('、') || []).map((point: string, index: number) => (
<Tag key={index} color="purple" style={{ fontSize: 12 }}>
{point}
</Tag>
))}
</div>
) : null}
</div>
<div style={{ display: 'flex' }}>
<span style={{ color: '#999', fontSize: 14, marginRight: 12, width: 80, flexShrink: 0 }}>:</span>
{taskDetail.analysisStatus === 'processing' ? (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<Spin size="small" tip="分析中" style={{ color: '#f59e0b' }} />
</div>
) : taskDetail.analysisStatus === 'completed' ? (
<span style={{ color: '#333' }}>{taskDetail.originalVideoContent}</span>
) : null}
</div> </div>
</div> </div>
<div style={{ display: 'flex', marginBottom: 12 }}>
<span style={{ color: '#64748b', fontSize: 14, marginRight: 12, width: 80, flexShrink: 0 }}>:</span>
<span style={{ color: '#1e293b', fontWeight: 500 }}>{taskDetail.title}</span>
</div>
<div style={{ display: 'flex', marginBottom: 12 }}>
<span style={{ color: '#64748b', fontSize: 14, marginRight: 12, width: 80, flexShrink: 0 }}>:</span>
{taskDetail.analysisStatus === 'processing' ? (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<Spin size="small" style={{ color: '#f59e0b' }} />
<span style={{ fontSize: 14, color: '#f59e0b' }}></span>
</div>
) : taskDetail.analysisStatus === 'completed' ? (
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
{(taskDetail.originalVideoAudience?.split('、') || []).map((point: string, index: number) => (
<Tag key={index} style={{ fontSize: 12, background: 'rgba(99, 102, 241, 0.1)', color: '#6366f1', border: 'none', borderRadius: 20 }}>
{point}
</Tag>
))}
</div>
) : null}
</div>
<div style={{ display: 'flex' }}>
<span style={{ color: '#64748b', fontSize: 14, marginRight: 12, width: 80, flexShrink: 0 }}>:</span>
{taskDetail.analysisStatus === 'processing' ? (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<Spin size="small" style={{ color: '#f59e0b' }} />
<span style={{ fontSize: 14, color: '#f59e0b' }}></span>
</div>
) : taskDetail.analysisStatus === 'completed' ? (
<span style={{ color: '#475569', lineHeight: 1.6 }}>{taskDetail.originalVideoContent}</span>
) : null}
</div>
</div> </div>
{/* </div> */} </div>
</div> </div>
<div style={{ display: 'flex', gap: 16, marginBottom: 20, marginTop: 20 }}> <div style={{ display: 'flex', gap: 16, marginBottom: 20, marginTop: 20 }}>
@@ -509,11 +643,23 @@ function RemoveInfo() {
disabled={!videoUrl || splitLoading} disabled={!videoUrl || splitLoading}
style={{ style={{
flex: 1, flex: 1,
height: 48, height: 52,
borderRadius: 8, borderRadius: 12,
borderColor: '#6366f1', border: '2px solid rgba(99, 102, 241, 0.3)',
color: '#6366f1', color: '#6366f1',
fontWeight: 500, fontWeight: 600,
fontSize: 15,
background: 'rgba(255, 255, 255, 0.9)',
backdropFilter: 'blur(10px)',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
}}
onMouseEnter={(e) => {
e.currentTarget.style.borderColor = '#6366f1';
e.currentTarget.style.boxShadow = '0 4px 20px rgba(99, 102, 241, 0.2)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = 'rgba(99, 102, 241, 0.3)';
e.currentTarget.style.boxShadow = 'none';
}} }}
> >
@@ -524,65 +670,141 @@ function RemoveInfo() {
cancelText="取消" cancelText="取消"
onConfirm={handleAutoGenerate} onConfirm={handleAutoGenerate}
> >
<div style={{ display: 'flex', alignItems: 'center', flex: 1 }}> <Button
<Button type="primary"
type="primary" loading={autoSplitLoading}
loading={autoSplitLoading} disabled={autoSplitLoading}
disabled={autoSplitLoading} style={{
style={{ flex: 1,
flex: 1, height: 52,
height: 48, borderRadius: 12,
borderRadius: 8, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%)',
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', fontWeight: 600,
fontWeight: 500, fontSize: 15,
border: 'none', border: 'none',
}} boxShadow: '0 4px 20px rgba(99, 102, 241, 0.3)',
> transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
{autoSplitButtonText} position: 'relative',
{taskDetail?.aiSuggestions && taskDetail.aiSuggestions.length > 0 && ( overflow: 'hidden',
<Tooltip }}
title={ onMouseEnter={(e) => {
<div> e.currentTarget.style.transform = 'translateY(-2px)';
<div style={{ fontWeight: 600, marginBottom: 8, color: '#6366f1' }}>AI拆分方案</div> e.currentTarget.style.boxShadow = '0 6px 25px rgba(99, 102, 241, 0.4)';
{(() => { }}
console.log('ai_suggestions:', taskDetail?.ai_suggestions); onMouseLeave={(e) => {
console.log('ai_suggestions2:', taskDetail?.aiSuggestions); e.currentTarget.style.transform = 'translateY(0)';
const suggestions = taskDetail?.ai_suggestions || taskDetail?.aiSuggestions || []; e.currentTarget.style.boxShadow = '0 4px 20px rgba(99, 102, 241, 0.3)';
return suggestions.map((item: any) => ( }}
<div key={item.index} style={{ marginBottom: 8, fontSize: 13, lineHeight: 1.5 }}> >
<span style={{ fontWeight: 500, color: '#6366f1' }}>{item.index}.</span> <span style={{ position: 'relative', zIndex: 1 }}>{autoSplitButtonText}</span>
<span style={{ marginLeft: 4, color: '#000000ff' }}>{item.timeNode}</span> {/* 按钮光效 */}
<br /> <div style={{
<span style={{ color: '#666' }}>{item.content}</span> position: 'absolute',
</div> top: 0,
)); left: '-100%',
})()} width: '100%',
</div> height: '100%',
} background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent)',
placement="top" transition: 'left 0.5s ease'
trigger="hover" }}
overlayInnerStyle={{ maxWidth: 400, background: '#fff', border: '1px solid #e5e7eb', borderRadius: 12, padding: 12 }} onMouseEnter={(e) => {
> e.currentTarget.style.left = '100%';
<div style={{ borderRadius: 8, background: 'rgba(99, 102, 241, 0.1)', color: '#ebebeb', fontSize: 13, fontWeight: 500, cursor: 'pointer' }}> }} />
(AI ) {taskDetail?.aiSuggestions && taskDetail.aiSuggestions.length > 0 && (
<Tooltip
title={
<div>
<div style={{ fontWeight: 600, marginBottom: 8, color: '#6366f1' }}>AI拆分方案</div>
{(() => {
const suggestions = taskDetail?.ai_suggestions || taskDetail?.aiSuggestions || [];
return suggestions.map((item: any) => (
<div key={item.index} style={{ marginBottom: 8, fontSize: 13, lineHeight: 1.5 }}>
<span style={{ fontWeight: 500, color: '#6366f1' }}>{item.index}.</span>
<span style={{ marginLeft: 4, color: '#000000ff' }}>{item.timeNode}</span>
<br />
<span style={{ color: '#666' }}>{item.content}</span>
</div>
));
})()}
</div> </div>
</Tooltip> }
placement="top"
trigger="hover"
>
<span style={{ marginLeft: 8, fontSize: 13, opacity: 0.9 }}>(AI )</span>
</Tooltip>
)} )}
</Button> </Button>
</div>
</Popconfirm> </Popconfirm>
</div> </div>
<div style={{ flex: 2, background: '#fff', borderRadius: 12, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}> <div
style={{
flex: 2,
background: 'linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.7) 100%)',
backdropFilter: 'blur(20px)',
borderRadius: 20,
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
border: '1px solid rgba(99, 102, 241, 0.1)',
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
}}
>
<div style={{ padding: '16px 24px', borderBottom: '1px solid rgba(99, 102, 241, 0.1)', background: 'linear-gradient(135deg, rgba(99,102,241,0.02) 0%, transparent 100%)' }}>
<h3 style={{ fontSize: 16, fontWeight: 600, color: '#1e293b', margin: 0 }}></h3>
<span style={{ fontSize: 12, color: '#64748b', marginLeft: 12 }}> {tableData.length} </span>
</div>
<Table <Table
columns={columns} columns={columns}
dataSource={tableData} dataSource={tableData}
pagination={false} pagination={false}
bordered={false} bordered={false}
rowKey="id" rowKey="id"
scroll={{ y: 450 }} scroll={{ y: 300 }}
style={{ flex: 1 }}
components={{
body: {
row: ({ className, style, ...rest }) => (
<tr
{...rest}
className={className}
style={{
...style,
transition: 'all 0.2s ease',
borderBottom: '1px solid rgba(99, 102, 241, 0.05)',
}}
/>
),
cell: ({ className, style, ...rest }) => (
<td
{...rest}
className={className}
style={{
...style,
padding: '16px 24px',
}}
/>
),
},
header: {
cell: ({ className, style, ...rest }) => (
<th
{...rest}
className={className}
style={{
...style,
background: 'rgba(99, 102, 241, 0.03)',
color: '#64748b',
fontWeight: 500,
fontSize: 13,
padding: '16px 24px',
borderBottom: 'none',
}}
/>
),
},
}}
/> />
</div> </div>
</div> </div>
@@ -607,15 +829,16 @@ function RemoveInfo() {
<Drawer <Drawer
title={ title={
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
<span> <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<span style={{ color: '#6366f1' }}></span> <div style={{ width: 4, height: 20, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
<span style={{ color: '#6366f1' }}>-{currentSegment}</span> <span style={{ fontSize: 18, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}></span>
</span> {currentSegmentName && <span style={{ fontSize: 14, color: '#64748b' }}>- {currentSegmentName}</span>}
</div>
<Button <Button
type="text" type="text"
icon={<XOutlined />} icon={<XOutlined />}
onClick={handleCloseDrawer} onClick={handleCloseDrawer}
style={{ padding: 0 }} style={{ padding: 0, color: '#64748b', fontSize: 14 }}
/> />
</div> </div>
} }
@@ -625,75 +848,88 @@ function RemoveInfo() {
open={drawerVisible} open={drawerVisible}
size={480} size={480}
styles={{ styles={{
body: { padding: '24px' }, body: { padding: '24px', background: 'linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%)' },
header: { background: '#fff', borderBottom: '1px solid rgba(99, 102, 241, 0.1)', padding: '20px 24px' },
content: { borderRadius: '20px 0 0 20px', overflow: 'hidden' },
}} }}
> >
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
<div> <div style={{ background: '#fff', borderRadius: 12, padding: 16, border: '1px solid rgba(99, 102, 241, 0.1)' }}>
<label style={{ fontWeight: 400, color: '#333', marginBottom: 12, display: 'block' }}> <label style={{ fontWeight: 500, color: '#334155', marginBottom: 8, display: 'block', fontSize: 14 }}>
<span style={{ color: '#ff4d4f' }}>*</span> <span style={{ color: '#ef4444' }}>*</span>
</label> </label>
<div style={{ display: 'flex', gap: 16 }}> <div style={{ display: 'flex', gap: 12 }}>
<Upload <Upload
listType="picture-card" listType="picture-card"
onChange={handleProductImageChange} onChange={handleProductImageChange}
beforeUpload={beforeUploadProductImage} beforeUpload={beforeUploadProductImage}
maxCount={1} maxCount={1}
accept="image/*" accept="image/*"
style={{ width: 140, height: 140 }} style={{ width: 120, height: 120, borderRadius: 8, border: '2px dashed rgba(99, 102, 241, 0.3)', background: 'rgba(99, 102, 241, 0.02)' }}
> >
{!productImage && ( {!productImage && (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6 }}>
<PlusOutlined style={{ fontSize: 24, color: '#999' }} /> <PlusOutlined style={{ fontSize: 20, color: '#6366f1' }} />
<span style={{ fontSize: 12, color: '#999' }}> *</span> <span style={{ fontSize: 12, color: '#64748b' }}></span>
</div> </div>
)} )}
</Upload> </Upload>
</div> </div>
</div> </div>
<div> <div style={{ background: '#fff', borderRadius: 12, padding: 16, border: '1px solid rgba(99, 102, 241, 0.1)' }}>
<label style={{ fontWeight: 500, color: '#333', marginBottom: 12, display: 'block' }}> <label style={{ fontWeight: 500, color: '#334155', marginBottom: 8, display: 'block', fontSize: 14 }}>
<span style={{ color: '#ef4444' }}>*</span>
</label> </label>
<Input <Input
value={productName} value={productName}
onChange={(e) => setProductName(e.target.value)} onChange={(e) => setProductName(e.target.value)}
placeholder="请输入产品名称" placeholder="请输入产品名称"
style={{ height: 48, borderRadius: 8 }} style={{ height: 44, borderRadius: 8, border: '1px solid rgba(99, 102, 241, 0.2)' }}
maxLength={10} maxLength={10}
showCount showCount
/> />
</div> </div>
<div> <div style={{ background: '#fff', borderRadius: 12, padding: 16, border: '1px solid rgba(99, 102, 241, 0.1)' }}>
<label style={{ fontWeight: 500, color: '#333', marginBottom: 12, display: 'block' }}> <label style={{ fontWeight: 500, color: '#334155', marginBottom: 8, display: 'block', fontSize: 14 }}>
<span style={{ color: '#ef4444' }}>*</span>
</label> </label>
<TextArea <TextArea
value={productSellingPoint} value={productSellingPoint}
onChange={(e) => setProductSellingPoint(e.target.value)} onChange={(e) => setProductSellingPoint(e.target.value)}
placeholder="请输入产品卖点" placeholder="请输入产品卖点"
style={{ borderRadius: 8 }} style={{ borderRadius: 8, border: '1px solid rgba(99, 102, 241, 0.2)' }}
maxLength={100} maxLength={100}
showCount showCount
rows={3} rows={3}
/> />
</div> </div>
<div style={{ display: 'flex', gap: 16, marginTop: 24 }}> <div style={{ display: 'flex', gap: 12, marginTop: 8 }}>
<Button <Button
type="default" type="primary"
onClick={handleManualGenerate} onClick={handleManualGenerate}
loading={loading} loading={loading}
disabled={loading} disabled={loading}
style={{ style={{
flex: 1, flex: 1,
height: 48, height: 48,
borderRadius: 8, borderRadius: 10,
borderColor: '#6366f1', background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
color: '#6366f1', fontWeight: 600,
fontWeight: 500, fontSize: 15,
border: 'none',
boxShadow: '0 4px 15px rgba(99, 102, 241, 0.3)',
transition: 'all 0.2s ease',
}}
onMouseEnter={(e) => {
e.currentTarget.style.boxShadow = '0 6px 20px rgba(99, 102, 241, 0.4)';
e.currentTarget.style.transform = 'translateY(-1px)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.boxShadow = '0 4px 15px rgba(99, 102, 241, 0.3)';
e.currentTarget.style.transform = 'translateY(0)';
}} }}
> >
{loading ? '生成中...' : '手动生成'} {loading ? '生成中...' : '手动生成'}
@@ -701,6 +937,42 @@ function RemoveInfo() {
</div> </div>
</div> </div>
</Drawer> </Drawer>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{ width: 4, height: 20, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
<span style={{ fontSize: 16, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}></span>
</div>
}
open={previewModalVisible}
onCancel={() => setPreviewModalVisible(false)}
afterClose={() => {
if (previewVideoRef.current) {
previewVideoRef.current.pause();
previewVideoRef.current.currentTime = 0;
}
}}
footer={null}
width={640}
centered
styles={{
body: { padding: 24, background: '#0f172a' },
header: { background: '#1e293b', borderBottom: 'none', padding: '20px 24px' },
}}
>
<div style={{ borderRadius: 12, overflow: 'hidden', boxShadow: '0 8px 32px rgba(0,0,0,0.3)' }}>
<video
ref={previewVideoRef}
controls
src={previewVideoUrl || ''}
style={{ width: '100%', maxHeight: 420, objectFit: 'contain', backgroundColor: '#000' }}
playsInline
webkit-playsinline
autoPlay
/>
</div>
</Modal>
</> </>
); );
} }
+335 -235
View File
@@ -83,7 +83,6 @@ export default function VideoFrameExtractor() {
await createShotReplication(params); await createShotReplication(params);
message.success('任务创建成功'); message.success('任务创建成功');
// 清空上传内容和输入框
cleanupResources(); cleanupResources();
} catch (err) { } catch (err) {
message.error('任务创建失败,请重试'); message.error('任务创建失败,请重试');
@@ -108,194 +107,194 @@ export default function VideoFrameExtractor() {
fetchList(page, size); fetchList(page, size);
}; };
// 打开弹窗时获取列表数据
const handleOpenModal = () => { const handleOpenModal = () => {
setIsModalOpen(true); setIsModalOpen(true);
fetchList(1, 10); fetchList(1, 10);
}; };
return ( return (
<div style={{ minHeight: 'calc(100vh - 90px)', overflow: 'auto', background: 'linear-gradient(135deg, #f5f3ff 0%, #fdf2f8 100%)', padding: '20px' }}> <div
<div style={{ maxWidth: 1200, margin: '0 auto' }}> style={{
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 24 }}> margin: '-24px -32px -32px',
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}> borderRadius: 20,
<div> minHeight: 'calc(100vh - 32px)',
<div style={{ overflow: 'auto',
width: 36, background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 50%, #f0f9ff 100%)',
height: 36, position: 'relative',
background: 'linear-gradient(135deg, #6366f1 0%, #ec4899 100%)', padding: '20px 32px',
borderRadius: 10, }}
display: 'flex', >
alignItems: 'center',
justifyContent: 'center',
color: 'white', <div style={{ maxWidth: 1200, margin: '50px auto', position: 'relative', zIndex: 10 }}>
fontSize: 18, <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 32 }}>
fontWeight: 600 <div style={{ display: 'flex', alignItems: 'center', gap: 16,marginBottom: 50 }}>
}}> <div style={{ width: 40, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} />
</div>
</div>
<div> <div>
<h1 style={{ <h1 style={{
fontSize: 22, fontSize: 24,
fontWeight: 600, fontWeight: 700,
margin: 0, margin: 0,
color: '#1e293b' background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text'
}}> }}>
AI视频拆镜工作台
</h1> </h1>
<p style={{ fontSize: 13, color: '#94a3b8', margin: 4 }}> <p style={{ fontSize: 13, color: '#64748b', margin: '4px 0 0 0' }}>
仿 仿
</p> </p>
</div> </div>
<div style={{ width: 40, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
</div> </div>
<button <Button
type="text"
icon={<FileTextOutlined />}
onClick={handleOpenModal} onClick={handleOpenModal}
style={{ style={{
padding: '10px 20px',
background: 'white',
border: '1px solid #e2e8f0',
borderRadius: 8,
cursor: 'pointer',
fontSize: 14, fontSize: 14,
color: '#6366f1', color: '#6366f1',
background: 'rgba(99, 102, 241, 0.1)',
borderRadius: 12,
padding: '10px 20px',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: 8, gap: 8,
boxShadow: '0 2px 8px rgba(0,0,0,0.06)' transition: 'all 0.2s ease',
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.15)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.1)';
}} }}
> >
<FileTextOutlined />
<span></span> </Button>
</button>
</div>
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: 32 }}>
<div style={{ textAlign: 'center' }}>
<div style={{
width: 56,
height: 56,
background: 'linear-gradient(135deg, #e0e7ff 0%, #fce7f3 100%)',
borderRadius: 16,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: '0 auto 16px'
}}>
<span style={{ fontSize: 28 }}>🎬</span>
</div>
<h2 style={{
fontSize: 22,
fontWeight: 600,
margin: 0,
background: 'linear-gradient(135deg, #6366f1 0%, #ec4899 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text'
}}>
</h2>
<p style={{ fontSize: 14, color: '#888', margin: '8px 0 0 0' }}>
MP4MOV 100MB
</p>
</div>
</div> </div>
<div style={{ width: '100%', maxWidth: 720, margin: '0 auto' }}> <div style={{ width: '100%', maxWidth: 720, margin: '0 auto' }}>
{error && ( {error && (
<div style={{ <div style={{
backgroundColor: '#fff5f5', background: 'linear-gradient(135deg, rgba(239, 68, 68, 0.1) 0%, rgba(239, 68, 68, 0.05) 100%)',
border: '1px solid #ffccc7', backdropFilter: 'blur(10px)',
borderRadius: 8, border: '1px solid rgba(239, 68, 68, 0.2)',
padding: '12px 16px', borderRadius: 12,
marginBottom: 20, padding: '14px 20px',
color: '#d93026', marginBottom: 24,
color: '#ef4444',
fontSize: 14, fontSize: 14,
textAlign: 'center' textAlign: 'center',
fontWeight: 500,
}}> }}>
{error} {error}
</div> </div>
)} )}
{!videoUrl && ( {!videoUrl && (
<Upload.Dragger <div
accept="video/*"
beforeUpload={handleFileChange}
showUploadList={false}
disabled={loading}
style={{ style={{
background: loading ? '#f3f4f6' : '#faf5ff', background: 'linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0.85) 100%)',
border: `2px dashed ${loading ? '#d1d5db' : '#c4b5fd'}`, backdropFilter: 'blur(20px)',
borderRadius: 24, borderRadius: 24,
padding: '60px 20px', border: '1px solid rgba(99, 102, 241, 0.1)',
cursor: loading ? 'not-allowed' : 'pointer', boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
position: 'relative',
overflow: 'hidden',
}} }}
> >
{loading ? ( <div style={{ position: 'absolute', top: -50, right: -50, width: 200, height: 200, background: 'radial-gradient(circle, rgba(99,102,241,0.05) 0%, transparent 70%)', borderRadius: '50%' }} />
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div style={{ <Upload.Dragger
width: 100, accept="video/*"
height: 100, beforeUpload={handleFileChange}
margin: '0 auto 20px', showUploadList={false}
background: '#e5e7eb', disabled={loading}
borderRadius: 24, style={{
display: 'flex', background: loading ? 'rgba(243, 244, 246, 0.3)' : 'transparent',
alignItems: 'center', border: `2px dashed ${loading ? 'rgba(209, 213, 219, 0.5)' : 'rgba(99, 102, 241, 0.3)'}`,
justifyContent: 'center' borderRadius: 24,
}}> padding: '56px 20px',
cursor: loading ? 'not-allowed' : 'pointer',
margin: 0,
}}
>
{loading ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div style={{ <div style={{
width: 40, width: 100,
height: 40, height: 100,
border: '4px solid #6366f1', margin: '0 auto 20px',
borderTopColor: 'transparent', background: 'linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(139, 92, 246, 0.1) 100%)',
borderRadius: '50%', borderRadius: 24,
animation: 'spin 1s linear infinite' display: 'flex',
}} /> alignItems: 'center',
justifyContent: 'center'
}}>
<div style={{
width: 40,
height: 40,
border: '4px solid #6366f1',
borderTopColor: 'transparent',
borderRadius: '50%',
animation: 'spin 1s linear infinite'
}} />
</div>
<p style={{ fontSize: 16, color: '#6366f1', margin: '0 0 8px 0', fontWeight: 600 }}>
...
</p>
<p style={{ fontSize: 13, color: '#64748b', margin: 0 }}>
</p>
</div> </div>
<p style={{ fontSize: 16, color: '#6366f1', margin: '0 0 8px 0', fontWeight: 500 }}> ) : (
... <>
</p> <div style={{
<p style={{ fontSize: 13, color: '#999', margin: 0 }}> width: 100,
height: 100,
</p> margin: '0 auto 20px',
</div> background: 'linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(139, 92, 246, 0.1) 100%)',
) : ( borderRadius: 24,
<> display: 'flex',
<div style={{ alignItems: 'center',
width: 100, justifyContent: 'center'
height: 100, }}>
margin: '0 auto 20px', <CloudUploadOutlined style={{ fontSize: 40, color: '#6366f1' }} />
background: 'linear-gradient(135deg, #e0e7ff 0%, #fce7f3 100%)', </div>
borderRadius: 24, <p style={{ fontSize: 16, color: '#1e293b', margin: '0 0 8px 0', fontWeight: 600 }}>
display: 'flex',
alignItems: 'center', </p>
justifyContent: 'center' <p style={{ fontSize: 13, color: '#64748b', margin: 0 }}>
}}> MP4MOV 100MB 3
<CloudUploadOutlined style={{ fontSize: 40, color: '#818cf8' }} /> </p>
</div> </>
<p style={{ fontSize: 16, color: '#333', margin: '0 0 8px 0', fontWeight: 500 }}> )}
</Upload.Dragger>
</p> </div>
<p style={{ fontSize: 13, color: '#999', margin: 0 }}>
MP4MOV 100MB 3
</p>
</>
)}
</Upload.Dragger>
)} )}
{videoUrl && ( {videoUrl && (
<div style={{ <div
position: 'relative', style={{
borderRadius: 24, background: 'linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0.85) 100%)',
padding: 4, backdropFilter: 'blur(20px)',
background: 'linear-gradient(135deg, #6366f1 0%, #ec4899 100%)' borderRadius: 24,
}}> border: '1px solid rgba(99, 102, 241, 0.1)',
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
overflow: 'hidden',
position: 'relative',
}}
>
<div style={{ position: 'absolute', top: -50, right: -50, width: 200, height: 200, background: 'radial-gradient(circle, rgba(99,102,241,0.05) 0%, transparent 70%)', borderRadius: '50%' }} />
<div style={{ <div style={{
borderRadius: 22, borderRadius: 22,
background: '#ffffff', background: '#ffffff',
padding: '24px' padding: '24px',
position: 'relative',
zIndex: 1,
}}> }}>
<Popconfirm <Popconfirm
title="确定要删除吗?" title="确定要删除吗?"
@@ -310,16 +309,27 @@ export default function VideoFrameExtractor() {
right: 20, right: 20,
width: 36, width: 36,
height: 36, height: 36,
border: '1px solid #e5e7eb', border: '1px solid rgba(99, 102, 241, 0.2)',
background: '#fff', background: 'rgba(99, 102, 241, 0.05)',
borderRadius: 10, borderRadius: 10,
cursor: 'pointer', cursor: 'pointer',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
fontSize: 16, fontSize: 16,
color: '#666', color: '#6366f1',
zIndex: 10 zIndex: 10,
transition: 'all 0.2s ease',
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'rgba(239, 68, 68, 0.1)';
e.currentTarget.style.borderColor = 'rgba(239, 68, 68, 0.3)';
e.currentTarget.style.color = '#ef4444';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.05)';
e.currentTarget.style.borderColor = 'rgba(99, 102, 241, 0.2)';
e.currentTarget.style.color = '#6366f1';
}} }}
> >
🗑 🗑
@@ -339,21 +349,21 @@ export default function VideoFrameExtractor() {
maxWidth: '100%', maxWidth: '100%',
maxHeight: 320, maxHeight: 320,
borderRadius: 12, borderRadius: 12,
background: '#000' background: '#000',
boxShadow: '0 4px 16px rgba(0,0,0,0.1)'
}} }}
/> />
</div> </div>
{/* 产品名称输入框 */}
<div style={{ marginBottom: 24 }}> <div style={{ marginBottom: 24 }}>
<label style={{ <label style={{
display: 'block', display: 'block',
fontSize: 14, fontSize: 14,
fontWeight: 500, fontWeight: 500,
color: '#374151', color: '#334155',
marginBottom: 8 marginBottom: 8
}}> }}>
<span style={{ color: '#ef4444' }}>*</span>
</label> </label>
<Input <Input
placeholder="请输入产品名称" placeholder="请输入产品名称"
@@ -361,9 +371,16 @@ export default function VideoFrameExtractor() {
onChange={(e) => setProductName(e.target.value)} onChange={(e) => setProductName(e.target.value)}
style={{ style={{
width: '100%', width: '100%',
height: 40, height: 44,
borderRadius: 8, borderRadius: 10,
borderColor: '#e5e7eb' border: '1px solid rgba(99, 102, 241, 0.2)',
transition: 'all 0.2s ease',
}}
onMouseEnter={(e) => {
e.currentTarget.style.borderColor = 'rgba(99, 102, 241, 0.4)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = 'rgba(99, 102, 241, 0.2)';
}} }}
/> />
</div> </div>
@@ -375,28 +392,34 @@ export default function VideoFrameExtractor() {
style={{ style={{
padding: '14px 56px', padding: '14px 56px',
background: videoUrl && productName.trim() && !loading background: videoUrl && productName.trim() && !loading
? 'linear-gradient(135deg, #6366f1 0%, #ec4899 100%)' ? 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%)'
: '#e2e8f0', : '#e2e8f0',
color: 'white', color: 'white',
border: 'none', border: 'none',
borderRadius: 14, borderRadius: 12,
fontSize: 15, fontSize: 15,
fontWeight: 600, fontWeight: 600,
cursor: videoUrl && productName.trim() && !loading ? 'pointer' : 'not-allowed', cursor: videoUrl && productName.trim() && !loading ? 'pointer' : 'not-allowed',
boxShadow: videoUrl && productName.trim() && !loading ? '0 6px 20px rgba(99, 102, 241, 0.35)' : 'none', boxShadow: videoUrl && productName.trim() && !loading ? '0 6px 20px rgba(99, 102, 241, 0.3)' : 'none',
display: 'inline-flex', display: 'inline-flex',
alignItems: 'center', alignItems: 'center',
gap: 10 gap: 10,
transition: 'all 0.3s ease',
}}
onMouseEnter={(e) => {
if (videoUrl && productName.trim() && !loading) {
e.currentTarget.style.transform = 'translateY(-2px)';
e.currentTarget.style.boxShadow = '0 8px 25px rgba(99, 102, 241, 0.4)';
}
}}
onMouseLeave={(e) => {
if (videoUrl && productName.trim() && !loading) {
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = '0 6px 20px rgba(99, 102, 241, 0.3)';
}
}} }}
> >
<span>{loading ? '创建中...' : '创建'}</span> <span>{loading ? '创建中...' : '创建'}</span>
<span style={{
fontSize: 12,
opacity: 0.85,
fontWeight: 400
}}>
{/* 预计消耗 10 个积分 */}
</span>
</button> </button>
</div> </div>
</div> </div>
@@ -406,91 +429,168 @@ export default function VideoFrameExtractor() {
<canvas ref={canvasRef} style={{ display: 'none' }} /> <canvas ref={canvasRef} style={{ display: 'none' }} />
<Modal <Modal
title="创作记录" title={
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{ width: 4, height: 20, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
<span style={{ fontSize: 16, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}></span>
</div>
}
open={isModalOpen} open={isModalOpen}
onCancel={() => setIsModalOpen(false)} onCancel={() => setIsModalOpen(false)}
width={800} width={800}
footer={null} footer={null}
styles={{
body: { background: 'linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%)' },
header: { background: '#fff', borderBottom: '1px solid rgba(99, 102, 241, 0.1)', padding: '20px 24px' },
}}
> >
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 16 }}> <div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 16, gap: 8 }}>
<Input <Input
placeholder="搜索产品名称" placeholder="搜索产品名称"
style={{ width: 200, borderRadius: 6, marginRight: 8 }} style={{
width: 200,
borderRadius: 10,
border: '1px solid rgba(99, 102, 241, 0.2)',
height: 40,
}}
/> />
<Button type="primary" style={{ borderRadius: 6 }}> <Button
type="primary"
style={{
borderRadius: 10,
height: 40,
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
border: 'none',
}}
>
</Button> </Button>
</div> </div>
<Table <div
columns={[ style={{
// { background: 'linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.7) 100%)',
// title: '产品图片', backdropFilter: 'blur(20px)',
// dataIndex: 'image', borderRadius: 16,
// key: 'image', overflow: 'hidden',
// width: 90, border: '1px solid rgba(99, 102, 241, 0.1)',
// render: (image: string) => ( boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
// <img }}
// src={image} >
// alt="产品图片" <Table
// style={{ width: 50, height: 50, objectFit: 'cover', borderRadius: 6 }} columns={[
// /> {
// ), title: '产品名称',
// }, dataIndex: 'title',
{ key: 'title',
title: '产品名称', render: (text: string) => (
dataIndex: 'title', <span style={{ fontSize: 14, color: '#1e293b', fontWeight: 500 }}>{text}</span>
key: 'title', ),
}, },
{ {
title: '状态', title: '创建时间',
dataIndex: 'status', dataIndex: 'createdAt',
key: 'status', key: 'createdAt',
}, render: (text: string) => {
{ if (!text) return '-';
title: '创建时间', const date = new Date(text);
dataIndex: 'createdAt', const year = date.getFullYear();
key: 'createdAt', const month = String(date.getMonth() + 1).padStart(2, '0');
render: (text: string) => { const day = String(date.getDate()).padStart(2, '0');
if (!text) return '-'; const hours = String(date.getHours()).padStart(2, '0');
const date = new Date(text); const minutes = String(date.getMinutes()).padStart(2, '0');
const year = date.getFullYear(); const seconds = String(date.getSeconds()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0'); return <span style={{ fontSize: 13, color: '#64748b' }}>{`${year}-${month}-${day} ${hours}:${minutes}:${seconds}`}</span>;
const day = String(date.getDate()).padStart(2, '0'); },
const hours = String(date.getHours()).padStart(2, '0'); },
const minutes = String(date.getMinutes()).padStart(2, '0'); {
const seconds = String(date.getSeconds()).padStart(2, '0'); title: '操作',
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; key: 'action',
}, align: 'center',
}, render: (record) => (
{ <Button
title: '操作', type="text"
key: 'action', onClick={() => navigate(`/removelens/${record.id}/removeinfo`)}
render: (record) => ( style={{
<button color: '#6366f1',
onClick={() => navigate(`/removelens/${record.id}/removeinfo`)} fontSize: 13,
style={{ color: '#6366f1', textDecoration: 'none', fontSize: 13, border: 'none', background: 'none', cursor: 'pointer' }} padding: '4px 12px',
> borderRadius: 6,
background: 'rgba(99, 102, 241, 0.1)',
</button> }}
), onMouseEnter={(e) => {
}, e.currentTarget.style.background = 'rgba(99, 102, 241, 0.15)';
]} }}
dataSource={tableData} onMouseLeave={(e) => {
rowKey="id" e.currentTarget.style.background = 'rgba(99, 102, 241, 0.1)';
pagination={{ }}
current: currentPage, >
pageSize: pageSize,
total: total, </Button>
showSizeChanger: true, ),
showQuickJumper: true, },
showTotal: (total) => `${total}`, ]}
onChange: handlePageChange, dataSource={tableData}
}} rowKey="id"
style={{ fontSize: 13 }} pagination={{
scroll={{ y: 350 }} current: currentPage,
pageSize: pageSize,
/> total: total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
onChange: handlePageChange,
style: {
padding: '16px 24px',
borderTop: '1px solid rgba(99, 102, 241, 0.1)',
},
}}
style={{ fontSize: 13 }}
scroll={{ y: 350 }}
components={{
body: {
row: ({ className, style, ...rest }) => (
<tr
{...rest}
className={className}
style={{
...style,
transition: 'all 0.2s ease',
borderBottom: '1px solid rgba(99, 102, 241, 0.05)',
}}
/>
),
cell: ({ className, style, ...rest }) => (
<td
{...rest}
className={className}
style={{
...style,
padding: '16px 24px',
}}
/>
),
},
header: {
cell: ({ className, style, ...rest }) => (
<th
{...rest}
className={className}
style={{
...style,
background: 'rgba(99, 102, 241, 0.03)',
color: '#64748b',
fontWeight: 500,
fontSize: 13,
padding: '16px 24px',
borderBottom: 'none',
}}
/>
),
},
}}
/>
</div>
</Modal> </Modal>
</div> </div>
</div> </div>
+360 -178
View File
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { Button, Typography, Collapse, Space, Modal, Input, Table, message } from 'antd'; import { Button, Typography, Collapse, Space, Modal, Input, Table, message } from 'antd';
import { ArrowLeftOutlined, PlayCircleOutlined, CheckCircleOutlined, EditOutlined, DownloadOutlined, SettingOutlined, LayoutOutlined } from '@ant-design/icons'; import { ArrowLeftOutlined, PlayCircleOutlined, CheckCircleOutlined, EditOutlined, DownloadOutlined, SettingOutlined, LayoutOutlined } from '@ant-design/icons';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import {getShotReplicationList, removeDetail, removeone, removetwo, removethree, removefour, getEngine, updateShotImagePrompt, updateShotVideoPromptSchema } from '../api/index'; import { getShotReplicationList, removeDetail, removeone, removetwo, removethree, removefour, getEngine, updateShotImagePrompt, updateShotVideoPromptSchema } from '../api/index';
import VideoPromptSchemaEditor from '../components/VideoPromptSchemaEditor'; import VideoPromptSchemaEditor from '../components/VideoPromptSchemaEditor';
import './css/InitialInfo.css'; import './css/InitialInfo.css';
@@ -325,7 +325,7 @@ function InitialInfo() {
// 获取引擎 ID 和视频参数 // 获取引擎 ID 和视频参数
const engineId = countType; const engineId = countType;
const videoParams = { const videoParams = {
engine_id : countType, engine_id: countType,
duration: videoDuration, duration: videoDuration,
aspect_ratio: videoAspectRatio, aspect_ratio: videoAspectRatio,
resolution: videoResolution, resolution: videoResolution,
@@ -340,7 +340,7 @@ function InitialInfo() {
// 这里可以添加下一步的逻辑,比如调用接口等 // 这里可以添加下一步的逻辑,比如调用接口等
}; };
const createvideo = (stepId: number,engineId: string) => { const createvideo = (stepId: number, engineId: string) => {
let params = { let params = {
engine_id: engineId, engine_id: engineId,
} }
@@ -369,31 +369,87 @@ function InitialInfo() {
return ( return (
<React.Fragment> <React.Fragment>
<div style={{ minHeight: 'calc(100vh - 90px)', background: '#f8fafc' }}> <div
<div style={{ height: '100%' }}> className="initialinfo-container"
<div style={{ height: '100%', display: 'flex', justifyContent: 'space-between', gap: '2%' }}> style={{
<div style={{ width: '70%', background: '#fff', borderRadius: 12, overflowY: 'auto', maxHeight: 'calc(100vh - 90px)' }}> margin: '-24px -32px -32px',
<div style={{ borderBottom: '1px solid #e2e8f0', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px', boxSizing: 'border-box' }}> height: 'calc(100vh - 34px)',
<Button position: 'relative',
type="text" boxSizing: 'border-box',
icon={<ArrowLeftOutlined />} overflow: 'hidden',
onClick={() => navigate(-1)} display: 'flex',
style={{ color: '#64748b' }} flexDirection: 'column',
> }}
>
</Button> {/* 背景装饰 */}
<h3 style={{ margin: 0 }}> <div style={{ position: 'absolute', top: -100, right: -100, width: 300, height: 300, background: 'radial-gradient(circle, rgba(99,102,241,0.08) 0%, transparent 70%)', borderRadius: '50%', pointerEvents: 'none' }} />
<div style={{ position: 'absolute', bottom: -100, left: -100, width: 300, height: 300, background: 'radial-gradient(circle, rgba(167,139,250,0.08) 0%, transparent 70%)', borderRadius: '50%', pointerEvents: 'none' }} />
<div style={{ flex: 1, display: 'flex', justifyContent: 'space-between', alignItems: 'stretch', gap: '2%', position: 'relative', zIndex: 1, minHeight: 0 }}>
<div
className="replication-preview"
style={{
flex: 4,
background: 'rgba(255,255,255,0.85)',
backdropFilter: 'blur(20px)',
borderRadius: 20,
overflow: 'hidden',
border: '1px solid rgba(99, 102, 241, 0.1)',
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
display: 'flex',
flexDirection: 'column',
minHeight: 0,
}}
>
<div style={{ borderBottom: '1px solid rgba(99, 102, 241, 0.08)', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px 24px', background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)', boxSizing: 'border-box' }}>
<Button
type="text"
icon={<ArrowLeftOutlined />}
onClick={() => navigate(-1)}
style={{
color: '#64748b',
borderRadius: 10,
fontSize: 13,
height: 32,
transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
}}
>
</Button>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} />
<h3 style={{ margin: 0, fontSize: 16, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
- -
</h3> </h3>
<Button type="primary" ghost style={{ borderRadius: 6, padding: '4px 12px', fontSize: 12, borderColor: '#6366f1', color: '#6366f1' }} onClick={() => setIsModalOpen(true)}> <div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
</Button>
</div> </div>
<Button
type="primary"
ghost
style={{
borderRadius: 10,
padding: '4px 16px',
fontSize: 13,
background: 'rgba(99, 102, 241, 0.1)',
borderColor: 'rgba(99, 102, 241, 0.2)',
color: '#6366f1',
}}
onClick={() => setIsModalOpen(true)}
>
</Button>
</div>
<div style={{ flex: 1, overflowY: 'auto' }}>
<div className="material_box"> <div className="material_box">
<div style={{ display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'flex-start' }}> <div style={{ display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'flex-start' }}>
<div style={{ flex: 1, minWidth: 250 }}> <div style={{ flex: 1, minWidth: 250 }}>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span> <span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<div className="medio_box" style={{ aspectRatio: '16/9', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}> <span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
</span>
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
{taskDetail?.material?.materialVideoUrl ? ( {taskDetail?.material?.materialVideoUrl ? (
<video <video
controls controls
@@ -401,26 +457,32 @@ function InitialInfo() {
style={{ width: '100%', height: '100%', objectFit: 'contain' }} style={{ width: '100%', height: '100%', objectFit: 'contain' }}
/> />
) : ( ) : (
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 12 }}></div> <div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 13 }}></div>
)} )}
</div> </div>
</div> </div>
<div className="product_img" style={{ flex: 1, minWidth: 250 }}> <div className="product_img" style={{ flex: 1, minWidth: 250 }}>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span> <span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<div className="medio_box" style={{ aspectRatio: '1/1', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}> <span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
</span>
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
{taskDetail?.material?.materialImageUrl ? ( {taskDetail?.material?.materialImageUrl ? (
<img src={taskDetail.material.materialImageUrl} alt="" style={{ width: '100%', height: '100%', objectFit: 'contain' }} /> <img src={taskDetail.material.materialImageUrl} alt="" style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
) : ( ) : (
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 12 }}></div> <div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 13 }}></div>
)} )}
</div> </div>
</div> </div>
</div> </div>
{taskDetail?.finalImageUrl && ( {taskDetail?.finalImageUrl && (
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 24 }}>
<div> <div>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span> <span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<div className="medio_box" style={{ aspectRatio: '1/1', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}> <span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
</span>
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
<img <img
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`} src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
alt="" alt=""
@@ -431,10 +493,13 @@ function InitialInfo() {
</div> </div>
)} )}
{taskDetail?.finalVideoUrl && ( {taskDetail?.finalVideoUrl && (
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 24 }}>
<div> <div>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span> <span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<div className="medio_box" style={{ aspectRatio: '16/9', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}> <span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
</span>
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
<video <video
controls controls
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`} src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
@@ -446,8 +511,28 @@ function InitialInfo() {
)} )}
</div> </div>
</div> </div>
<div style={{ width: '28%', minWidth: '400px', background: '#fff', borderRadius: 12, overflowY: 'auto', maxHeight: 'calc(100vh - 90px)' }}> </div>
<div style={{ padding: '14px' }}> </div> <div
className="replication-form"
style={{
flex: 1,
minWidth: '400px',
background: 'rgba(255,255,255,0.85)',
backdropFilter: 'blur(20px)',
borderRadius: 20,
overflow: 'hidden',
border: '1px solid rgba(99, 102, 241, 0.1)',
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
display: 'flex',
flexDirection: 'column',
minHeight: 0,
}}
>
<div style={{ padding: '16px 24px', borderBottom: '1px solid rgba(99, 102, 241, 0.08)', display: 'flex', alignItems: 'center', gap: 10, background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)' }}>
<div style={{ width: 4, height: 18, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
<span style={{ fontSize: 15, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}></span>
</div>
<div style={{ flex: 1, overflowY: 'auto', padding: '8px 16px 16px' }}>
<Collapse <Collapse
defaultActiveKey={['']} defaultActiveKey={['']}
ghost ghost
@@ -488,14 +573,14 @@ function InitialInfo() {
</div> </div>
), ),
children: ( children: (
<div style={{ padding: '16px', background: '#f8fafc', borderRadius: 8, marginTop: 8 }}> <div style={{ padding: '16px', background: 'linear-gradient(135deg, rgba(248,250,252,0.6) 0%, rgba(238,242,255,0.4) 100%)', borderRadius: 12, marginTop: 8, border: '1px solid rgba(99, 102, 241, 0.06)' }}>
{/* 步骤1: 原始素材 */} {/* 步骤1: 原始素材 */}
{step.childId === 1 && ( {step.childId === 1 && (
<> <>
<div style={{ display: 'flex', gap: 20, marginBottom: 20, alignItems: 'flex-start', flexWrap: 'wrap' }}> <div style={{ display: 'flex', gap: 20, marginBottom: 20, alignItems: 'flex-start', flexWrap: 'wrap' }}>
<div style={{ flex: 1, minWidth: 200 }}> <div style={{ flex: 1, minWidth: 200 }}>
<Text style={{ color: '#64748b', fontSize: 13, marginBottom: 10, display: 'block', fontWeight: 500 }}></Text> <Text style={{ color: '#64748b', fontSize: 13, marginBottom: 10, display: 'block', fontWeight: 500 }}></Text>
<div style={{ height: 180, borderRadius: 10, background: '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <div style={{ height: 180, borderRadius: 12, background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', display: 'flex', alignItems: 'center', justifyContent: 'center', border: '1px solid rgba(99, 102, 241, 0.06)' }}>
{taskDetail?.material?.materialVideoUrl ? ( {taskDetail?.material?.materialVideoUrl ? (
<video <video
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.material.materialVideoUrl}`} src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.material.materialVideoUrl}`}
@@ -505,8 +590,8 @@ function InitialInfo() {
) : ( ) : (
<div style={{ textAlign: 'center', color: '#94a3b8' }}> <div style={{ textAlign: 'center', color: '#94a3b8' }}>
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" style={{ marginBottom: 8 }}> <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" style={{ marginBottom: 8 }}>
<rect x="2" y="2" width="20" height="16" rx="2" ry="2"/> <rect x="2" y="2" width="20" height="16" rx="2" ry="2" />
<path d="M8 12l4 2 4-2"/> <path d="M8 12l4 2 4-2" />
</svg> </svg>
<div style={{ fontSize: 13 }}></div> <div style={{ fontSize: 13 }}></div>
</div> </div>
@@ -515,7 +600,7 @@ function InitialInfo() {
</div> </div>
<div style={{ flex: 1, minWidth: 200 }}> <div style={{ flex: 1, minWidth: 200 }}>
<Text style={{ color: '#64748b', fontSize: 13, marginBottom: 10, display: 'block', fontWeight: 500 }}></Text> <Text style={{ color: '#64748b', fontSize: 13, marginBottom: 10, display: 'block', fontWeight: 500 }}></Text>
<div style={{ height: 180, borderRadius: 10, background: '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <div style={{ height: 180, borderRadius: 12, background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', display: 'flex', alignItems: 'center', justifyContent: 'center', border: '1px solid rgba(99, 102, 241, 0.06)' }}>
{taskDetail?.material?.materialImageUrl ? ( {taskDetail?.material?.materialImageUrl ? (
<img <img
src={taskDetail.material.materialImageUrl} src={taskDetail.material.materialImageUrl}
@@ -525,16 +610,16 @@ function InitialInfo() {
) : ( ) : (
<div style={{ textAlign: 'center', color: '#94a3b8' }}> <div style={{ textAlign: 'center', color: '#94a3b8' }}>
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" style={{ marginBottom: 8 }}> <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" style={{ marginBottom: 8 }}>
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/> <rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
<circle cx="8.5" cy="8.5" r="1.5" fill="currentColor"/> <circle cx="8.5" cy="8.5" r="1.5" fill="currentColor" />
<path d="M21 15l-5-5L5 21"/> <path d="M21 15l-5-5L5 21" />
</svg> </svg>
<div style={{ fontSize: 13 }}></div> <div style={{ fontSize: 13 }}></div>
</div> </div>
)} )}
</div> </div>
</div> </div>
<Button onClick={() => { message.info('正在生成图片提示词,请稍候...'); createone(step.id); }} type="primary" style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }} disabled={step.status !== 'completed'}> <Button onClick={() => { message.info('正在生成图片提示词,请稍候...'); createone(step.id); }} type="primary" style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }} disabled={step.status !== 'completed'}>
</Button> </Button>
</div> </div>
@@ -544,24 +629,22 @@ function InitialInfo() {
{/* 步骤2: 生成提示词 */} {/* 步骤2: 生成提示词 */}
{step.childId === 2 && ( {step.childId === 2 && (
<> <>
{/* <Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}> <div style={{ padding: '12px 14px', background: 'rgba(255,255,255,0.6)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.08)', marginBottom: 4 }}>
{taskDetail?.generationPrompt || '暂无提示词'} <Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
</Text> */} {step?.output?.payload?.prompt || '暂无提示词'}
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}> </Text>
{/* {taskDetail?.generationPrompt || '暂无提示词'} */} </div>
{step?.output?.payload?.prompt || '暂无提示词'}
</Text>
<Space style={{ marginTop: 16, gap: 12, width: '100%' }}> <Space style={{ marginTop: 16, gap: 12, width: '100%' }}>
<Button <Button
type="default" type="default"
icon={<EditOutlined />} icon={<EditOutlined />}
onClick={() => handleOpenModal(step?.output?.payload?.prompt, 'image', step.id)} onClick={() => handleOpenModal(step?.output?.payload?.prompt, 'image', step.id)}
style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }} style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }}
disabled={step.status !== 'completed'} disabled={step.status !== 'completed'}
> >
</Button> </Button>
<Button onClick={() => { message.info('正在生成图片,请稍候...'); createimage(step.id); }} type="primary" style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }} disabled={step.status !== 'completed'}> <Button onClick={() => { message.info('正在生成图片,请稍候...'); createimage(step.id); }} type="primary" style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }} disabled={step.status !== 'completed'}>
</Button> </Button>
</Space> </Space>
@@ -570,19 +653,39 @@ function InitialInfo() {
{/* 步骤3: 生成产品融合图 */} {/* 步骤3: 生成产品融合图 */}
{step.childId === 3 && ( {step.childId === 3 && (
<> <>
<div style={{ borderRadius: 8, overflow: 'hidden', marginBottom: 16, background: '#f1f5f9' }}> <div style={{ borderRadius: 12, overflow: 'hidden', marginBottom: 16, background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', border: '1px solid rgba(99, 102, 241, 0.08)', boxShadow: '0 2px 8px rgba(99, 102, 241, 0.04)', position: 'relative' }}>
{/* {step.output.result.result_image_url} */}
{taskDetail.finalImageUrl ? ( {taskDetail.finalImageUrl ? (
<img src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`} alt="融合图" style={{ width: '100%', height: 180, objectFit: 'cover' }} /> <img src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`} alt="融合图" style={{ width: '100%', height: 180, objectFit: 'cover' }} />
) : ( ) : (
<div style={{ width: '100%', height: 180, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 14 }}></div> <div style={{ width: '100%', height: 180, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 14 }}></div>
)} )}
<Button
type="default"
icon={<EditOutlined />}
onClick={() => newcreateimage()}
disabled={step.status !== 'completed'}
style={{
position: 'absolute',
bottom: 8,
left: 8,
borderRadius: 8,
borderColor: 'rgba(99, 102, 241, 0.3)',
color: '#6366f1',
height: 30,
fontWeight: 500,
background: 'rgba(255,255,255,0.85)',
backdropFilter: 'blur(4px)',
padding: '0 12px',
fontSize: 12,
}}
>
</Button>
</div> </div>
{/* 引擎选择器 */} {/* 引擎选择器和视频参数设置 */}
<div style={{ marginBottom: 12 }}> <p style={{ marginBottom: 6, fontSize: 14, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}></p>
<div style={{ position: 'relative', display: 'inline-block' }}> <div style={{ width: '100%', display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
<button <button
onClick={() => { onClick={() => {
setShowEngineModal(!showEngineModal); setShowEngineModal(!showEngineModal);
@@ -590,12 +693,12 @@ function InitialInfo() {
}} }}
className="image-settings-trigger" className="image-settings-trigger"
style={{ style={{
minWidth: 200, width: '100%',
padding: '4px 12px', padding: '4px 12px',
height: 34, height: 34,
borderRadius: 8, borderRadius: 10,
border: 'none', border: '1px solid rgba(99, 102, 241, 0.15)',
backgroundColor: '#f1f5f9', backgroundColor: 'rgba(255,255,255,0.7)',
cursor: 'pointer', cursor: 'pointer',
display: 'inline-flex', display: 'inline-flex',
alignItems: 'center', alignItems: 'center',
@@ -603,7 +706,7 @@ function InitialInfo() {
transition: 'all 0.2s', transition: 'all 0.2s',
}} }}
> >
<SettingOutlined style={{ fontSize: 14, color: '#64748b' }} /> <SettingOutlined style={{ fontSize: 14, color: '#6366f1' }} />
<Text style={{ <Text style={{
fontSize: 14, fontSize: 14,
fontWeight: 500, fontWeight: 500,
@@ -619,7 +722,7 @@ function InitialInfo() {
style={{ style={{
position: 'absolute', position: 'absolute',
bottom: 'calc(100% + 8px)', bottom: 'calc(100% + 8px)',
left: -20, left: -10,
width: 350, width: 350,
backgroundColor: '#fff', backgroundColor: '#fff',
borderRadius: 16, borderRadius: 16,
@@ -709,11 +812,9 @@ function InitialInfo() {
</div> </div>
)} )}
</div> </div>
</div>
{/* 视频参数设置 */} {/* 视频参数设置 */}
<div style={{ marginBottom: 16 }}> <div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
<div style={{ position: 'relative', display: 'inline-block' }}>
<button <button
onClick={() => { onClick={() => {
setShowVideoSettingsModal(!showVideoSettingsModal); setShowVideoSettingsModal(!showVideoSettingsModal);
@@ -721,12 +822,12 @@ function InitialInfo() {
}} }}
className="image-settings-trigger" className="image-settings-trigger"
style={{ style={{
minWidth: 220, width: '100%',
padding: '4px 12px', padding: '4px 12px',
height: 34, height: 34,
borderRadius: 8, borderRadius: 10,
border: 'none', border: '1px solid rgba(99, 102, 241, 0.15)',
backgroundColor: '#f1f5f9', backgroundColor: 'rgba(255,255,255,0.7)',
cursor: 'pointer', cursor: 'pointer',
display: 'inline-flex', display: 'inline-flex',
alignItems: 'center', alignItems: 'center',
@@ -734,7 +835,7 @@ function InitialInfo() {
transition: 'all 0.2s', transition: 'all 0.2s',
}} }}
> >
<LayoutOutlined style={{ fontSize: 14, color: '#64748b' }} /> <LayoutOutlined style={{ fontSize: 14, color: '#6366f1' }} />
<Text style={{ <Text style={{
fontSize: 14, fontSize: 14,
fontWeight: 500, fontWeight: 500,
@@ -750,7 +851,7 @@ function InitialInfo() {
style={{ style={{
position: 'absolute', position: 'absolute',
bottom: 'calc(100% + 8px)', bottom: 'calc(100% + 8px)',
left: -20, left: -160,
width: 350, width: 350,
backgroundColor: '#fff', backgroundColor: '#fff',
borderRadius: 16, borderRadius: 16,
@@ -942,41 +1043,35 @@ function InitialInfo() {
)} )}
</div> </div>
</div> </div>
<Space style={{ width: '100%', gap: 12 }}> <Button
<Button type="default" icon={<EditOutlined />} style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 } type="primary"
} onClick={() => newcreateimage()} disabled={step.status !== 'completed'}> style={{ width: '100%', borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
onClick={() => { message.info('正在生成视频提示词,请稍候...'); handleNextStep(step.id); }}
</Button> disabled={step.status !== 'completed'}
<Button >
type="primary"
style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }} </Button>
onClick={() => { message.info('正在生成视频提示词,请稍候...'); handleNextStep(step.id); }}
disabled={step.status !== 'completed'}
>
</Button>
</Space>
</> </>
)} )}
{/* 步骤4: 生成视频提示词 */} {/* 步骤4: 生成视频提示词 */}
{step.childId === 4 && ( {step.childId === 4 && (
<> <>
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}> <div style={{ padding: '12px 14px', background: 'rgba(255,255,255,0.6)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.08)', marginBottom: 4 }}>
{/* {taskDetail?.videoPrompt || '暂无视频提示词'} */} <Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
{step?.output?.payload?.finalPrompt || '暂无提示词'} {step?.output?.payload?.finalPrompt || '暂无提示词'}
</Text>
</Text> </div>
<Space style={{ marginTop: 16, gap: 12, width: '100%' }}> <Space style={{ marginTop: 16, gap: 12, width: '100%' }}>
<Button <Button
type="default" type="default"
icon={<EditOutlined />} icon={<EditOutlined />}
onClick={() => handleOpenModal(step?.output?.payload?.promptSchema, 'video', step.id)} onClick={() => handleOpenModal(step?.output?.payload?.promptSchema, 'video', step.id)}
style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }} style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }}
disabled={step.status !== 'completed'} disabled={step.status !== 'completed'}
> >
</Button> </Button>
<Button onClick={() => { message.info('正在生成视频,请稍候...'); createvideo(step.id,step.engineId); }} type="primary" style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }} disabled={step.status !== 'completed'}> <Button onClick={() => { message.info('正在生成视频,请稍候...'); createvideo(step.id, step.engineId); }} type="primary" style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }} disabled={step.status !== 'completed'}>
</Button> </Button>
</Space> </Space>
@@ -985,7 +1080,7 @@ function InitialInfo() {
{/* 步骤5: 生成最终视频 */} {/* 步骤5: 生成最终视频 */}
{step.childId === 5 && ( {step.childId === 5 && (
<> <>
<div style={{ borderRadius: 8, overflow: 'hidden', marginBottom: 16, position: 'relative', background: '#f1f5f9' }}> <div style={{ borderRadius: 12, overflow: 'hidden', marginBottom: 16, position: 'relative', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', border: '1px solid rgba(99, 102, 241, 0.08)', boxShadow: '0 2px 8px rgba(99, 102, 241, 0.04)' }}>
{step.status === 'completed' && taskDetail?.finalVideoUrl ? ( {step.status === 'completed' && taskDetail?.finalVideoUrl ? (
<video src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`} style={{ width: '100%', height: 180, objectFit: 'cover' }} controls /> <video src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`} style={{ width: '100%', height: 180, objectFit: 'cover' }} controls />
) : step.status === 'processing' ? ( ) : step.status === 'processing' ? (
@@ -1000,13 +1095,13 @@ function InitialInfo() {
)} )}
</div> </div>
<Space style={{ width: '100%', gap: 12 }}> <Space style={{ width: '100%', gap: 12 }}>
<Button onClick={() => agincreatevideo()} type="default" icon={<EditOutlined />} style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }} disabled={step.status !== 'completed'}> <Button onClick={() => agincreatevideo()} type="default" icon={<EditOutlined />} style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }} disabled={step.status !== 'completed'}>
</Button> </Button>
<Button <Button
type="primary" type="primary"
icon={<DownloadOutlined />} icon={<DownloadOutlined />}
style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }} style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
disabled={step.status !== 'completed'} disabled={step.status !== 'completed'}
onClick={() => { onClick={() => {
const videoUrl = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail?.finalVideoUrl}&download=1`; const videoUrl = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail?.finalVideoUrl}&download=1`;
@@ -1032,21 +1127,33 @@ function InitialInfo() {
</div> </div>
</div> </div>
<Modal <Modal
title="修改提示词" title={
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 4, height: 18, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
<span style={{ fontSize: 15, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
{currentType === 'video' ? '修改视频提示词' : '修改提示词'}
</span>
</div>
}
open={modalVisible} open={modalVisible}
onCancel={() => setModalVisible(false)} onCancel={() => { if (!promptSaving) setModalVisible(false); }}
footer={[ footer={[
<Button key="cancel" onClick={() => setModalVisible(false)}></Button>, <Button key="cancel" onClick={() => setModalVisible(false)} disabled={promptSaving} style={{ borderRadius: 8 }}></Button>,
<Button key="confirm" type="primary" onClick={handleConfirm} loading={promptSaving}></Button>, <Button key="confirm" type="primary" onClick={handleConfirm} loading={promptSaving} disabled={promptSaving} style={{ borderRadius: 8, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none' }}></Button>,
]} ]}
width={800} width={800}
style={{ borderRadius: 16 }}
styles={{
header: { background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)', borderBottom: '1px solid rgba(99, 102, 241, 0.08)', padding: '16px 24px' },
body: { background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 50%, #f0f9ff 100%)', padding: '20px 24px' },
}}
> >
{currentType === 'image' ? ( {currentType === 'image' ? (
<TextArea <TextArea
value={promptText} value={promptText}
onChange={(e) => setPromptText(e.target.value)} onChange={(e) => setPromptText(e.target.value)}
rows={8} rows={8}
style={{ width: '100%', height: 300 }} style={{ width: '100%', height: 300, borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.15)', background: 'rgba(255,255,255,0.8)' }}
placeholder="请输入提示词" placeholder="请输入提示词"
/> />
) : ( ) : (
@@ -1057,93 +1164,168 @@ function InitialInfo() {
</Modal> </Modal>
{/* 创作记录弹窗 */} {/* 创作记录弹窗 */}
<Modal <Modal
title="创作记录" title={
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{ width: 4, height: 20, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
<span style={{ fontSize: 16, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}></span>
</div>
}
open={isModalOpen} open={isModalOpen}
onCancel={() => setIsModalOpen(false)} onCancel={() => setIsModalOpen(false)}
width={800} width={800}
footer={null} footer={null}
style={{ borderRadius: 12 }} styles={{
body: { background: 'linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%)' },
header: { background: '#fff', borderBottom: '1px solid rgba(99, 102, 241, 0.1)', padding: '20px 24px' },
}}
> >
{/* 搜索区域 */} <div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 16, gap: 8 }}>
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 16 }}>
<Input <Input
placeholder="搜索产品名称" placeholder="搜索产品名称"
style={{ width: 200, borderRadius: 6, marginRight: 8 }} style={{
width: 200,
borderRadius: 10,
border: '1px solid rgba(99, 102, 241, 0.2)',
height: 40,
}}
/> />
<Button type="primary" style={{ borderRadius: 6 }}> <Button
type="primary"
style={{
borderRadius: 10,
height: 40,
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
border: 'none',
}}
>
</Button> </Button>
</div> </div>
{/* 表格 */} <div
<Table style={{
columns={[ background: 'linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.7) 100%)',
{ backdropFilter: 'blur(20px)',
title: '产品名称', borderRadius: 16,
dataIndex: 'targetProjectName', overflow: 'hidden',
key: 'targetProjectName', border: '1px solid rgba(99, 102, 241, 0.1)',
}, boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (text: string) => {
const statusMap: Record<string, string> = {
'pending': '子任务待处理',
'waiting_user': '等待用户确认或触发',
'processing': '子任务处理中',
'completed': '子任务完成',
'failed': '子任务失败',
'cancelled': '子任务取消',
};
return statusMap[text] || text || '-';
},
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
render: (text: string) => {
if (!text) return '-';
const date = new Date(text);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
},
},
{
title: '操作',
key: 'action',
render: (_, record) => (
<button
onClick={() => {
setIsModalOpen(false);
navigate(`/initial/${record.id}/initialinfo`);
}}
style={{ color: '#6366f1', textDecoration: 'none', fontSize: 12, border: 'none', background: 'none', cursor: 'pointer' }}
>
</button>
),
},
]}
dataSource={tableData}
rowKey="id"
pagination={{
current: currentPage,
pageSize: pageSize,
total: total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
onChange: handlePageChange,
}} }}
style={{ fontSize: 13 }} >
/> <Table
columns={[
{
title: '产品名称',
dataIndex: 'title',
key: 'title',
render: (text: string) => (
<span style={{ fontSize: 14, color: '#1e293b', fontWeight: 500 }}>{text}</span>
),
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
render: (text: string) => {
if (!text) return '-';
const date = new Date(text);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return <span style={{ fontSize: 13, color: '#64748b' }}>{`${year}-${month}-${day} ${hours}:${minutes}:${seconds}`}</span>;
},
},
{
title: '操作',
key: 'action',
align: 'center',
render: (record) => (
<Button
type="text"
onClick={() => navigate(`/removelens/${record.id}/removeinfo`)}
style={{
color: '#6366f1',
fontSize: 13,
padding: '4px 12px',
borderRadius: 6,
background: 'rgba(99, 102, 241, 0.1)',
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.15)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.1)';
}}
>
</Button>
),
},
]}
dataSource={tableData}
rowKey="id"
pagination={{
current: currentPage,
pageSize: pageSize,
total: total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
onChange: handlePageChange,
style: {
padding: '16px 24px',
borderTop: '1px solid rgba(99, 102, 241, 0.1)',
},
}}
style={{ fontSize: 13 }}
scroll={{ y: 350 }}
components={{
body: {
row: ({ className, style, ...rest }) => (
<tr
{...rest}
className={className}
style={{
...style,
transition: 'all 0.2s ease',
borderBottom: '1px solid rgba(99, 102, 241, 0.05)',
}}
/>
),
cell: ({ className, style, ...rest }) => (
<td
{...rest}
className={className}
style={{
...style,
padding: '16px 24px',
}}
/>
),
},
header: {
cell: ({ className, style, ...rest }) => (
<th
{...rest}
className={className}
style={{
...style,
background: 'rgba(99, 102, 241, 0.03)',
color: '#64748b',
fontWeight: 500,
fontSize: 13,
padding: '16px 24px',
borderBottom: 'none',
}}
/>
),
},
}}
/>
</div>
</Modal> </Modal>
</React.Fragment> </React.Fragment>
); );
+95 -5
View File
@@ -1,26 +1,116 @@
*{ * {
box-sizing: border-box; box-sizing: border-box;
} }
.material_box>div{
margin: 20px 10px; .material_box {
padding: 24px;
} }
.material_box>div {
margin: 0 0 20px 0;
}
.medio_box { .medio_box {
width: 280px; width: 280px;
height: 150px; height: 150px;
margin-top: 6px; margin-top: 6px;
} }
.medio_box img ,.medio_box video {
.medio_box img,
.medio_box video {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
/* 加载旋转动画 */ /* 加载旋转动画 */
@keyframes spinSlow { @keyframes spinSlow {
from { from {
transform: rotate(0deg); transform: rotate(0deg);
} }
to { to {
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
/* 步骤状态动画 */
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.6;
}
}
/* 折叠面板样式优化 */
.ant-collapse-ghost > .ant-collapse-item {
border-bottom: 1px solid rgba(99, 102, 241, 0.06);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.ant-collapse-ghost > .ant-collapse-item:hover {
background: rgba(99, 102, 241, 0.02);
}
.ant-collapse-ghost > .ant-collapse-item > .ant-collapse-header {
padding: 14px 16px !important;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* 步骤内容区滚动条 */
.replication-form-content::-webkit-scrollbar,
.replication-preview::-webkit-scrollbar {
width: 6px;
}
.replication-form-content::-webkit-scrollbar-thumb,
.replication-preview::-webkit-scrollbar-thumb {
background: rgba(99, 102, 241, 0.2);
border-radius: 3px;
}
.replication-form-content::-webkit-scrollbar-thumb:hover,
.replication-preview::-webkit-scrollbar-thumb:hover {
background: rgba(99, 102, 241, 0.4);
}
.replication-form-content::-webkit-scrollbar-track,
.replication-preview::-webkit-scrollbar-track {
background: transparent;
}
/* 滑块样式 */
input[type="range"] {
-webkit-appearance: none;
appearance: none;
background: transparent;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: #6366f1;
cursor: pointer;
border: 2px solid #fff;
box-shadow: 0 2px 6px rgba(99, 102, 241, 0.4);
transition: all 0.2s;
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.15);
box-shadow: 0 3px 10px rgba(99, 102, 241, 0.6);
}
input[type="range"]::-moz-range-thumb {
width: 16px;
height: 16px;
border-radius: 50%;
background: #6366f1;
cursor: pointer;
border: 2px solid #fff;
box-shadow: 0 2px 6px rgba(99, 102, 241, 0.4);
}