爆款开头复刻样式修改

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 style={{ marginTop: -54, padding: '0 8px 0' }}>
<div style={{ marginTop: 0, padding: '0 8px 0' }}>
<Slider
range
min={0}
@@ -616,7 +616,7 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
marginTop: 0,
// backgroundColor: '#fff',
// 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)',
},
}}
+4 -5
View File
@@ -830,7 +830,7 @@ const AIChatPage: React.FC = () => {
// ==================== 渲染 ====================
return (
<Layout style={{ height: 'calc(100vh - 90px)', background: '#fafafa', overflow: 'auto' }}>
<Layout style={{ height: 'calc(100vh - 90px)', overflow: 'auto' }}>
{/* 左侧边栏 - 对话列表(已隐藏,保留代码) */}
{false && (
<Sider
@@ -917,12 +917,12 @@ const AIChatPage: React.FC = () => {
)}
{/* 主内容区 */}
<Layout style={{ flex: 1 }}>
<Layout style={{ flex: 1, background: '#ffffffff' }}>
{/* 头部 - 显示对话标题和模型信息 */}
<Header
style={{
background: '#fff',
padding: '0 24px',
// padding: '0 24px',
borderBottom: '1px solid #f0f0f0',
display: 'flex',
alignItems: 'center',
@@ -945,10 +945,9 @@ const AIChatPage: React.FC = () => {
<Content
style={{
margin: 0,
background: '#fafafa',
display: 'flex',
flexDirection: 'column',
padding: 24,
// padding: 24,
overflow: 'hidden',
}}
>
+112 -39
View File
@@ -1,5 +1,6 @@
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 {
SearchOutlined,
FilterOutlined,
@@ -32,6 +33,7 @@ const GeneratedRecord: React.FC = () => {
const [previewVisible, setPreviewVisible] = useState(false);
const [previewItem, setPreviewItem] = useState<any>(null);
const videoRef = React.createRef<HTMLVideoElement>();
const [selectedDate, setSelectedDate] = useState<string>('');
// 批量上传相关状态
const [uploadModalVisible, setUploadModalVisible] = useState(false);
@@ -52,7 +54,7 @@ const GeneratedRecord: React.FC = () => {
authStatus: 'pending' | 'authorized' | 'failed';
}[]>([]);
const [accountIdInput, setAccountIdInput] = useState('');
const [oauthList, setOauthList] = useState<any[]>([]);
const [oauthLoading, setOauthLoading] = useState(false);
const [oauthTotal, setOauthTotal] = useState(0);
@@ -60,7 +62,7 @@ const GeneratedRecord: React.FC = () => {
const [oauthPage, setOauthPage] = useState(1);
const [oauthPageSize, setOauthPageSize] = useState(10);
const [oauthSelectOpen, setOauthSelectOpen] = useState(false);
const [batchUploadProgress, setBatchUploadProgress] = useState<{
itemId: string;
status: 'pending' | 'uploading' | 'success' | 'error';
@@ -290,7 +292,7 @@ const GeneratedRecord: React.FC = () => {
borderRadius: 4,
cursor: 'pointer',
overflow: 'hidden',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
transition: 'transform 0.2s, box-shadow 0.2s',
}}
onClick={!isSelectionMode ? onClick : undefined}
@@ -307,7 +309,7 @@ const GeneratedRecord: React.FC = () => {
}
}}
>
{/* 媒体内容 - 当isLoading为true时开始渲染,加载完成后显示 */}
{((mediaType === 'video' && item.videoCoverUrl) || mediaType === 'image') && (isLoading || isLoaded) && !isError && (
<div style={{
@@ -664,7 +666,7 @@ const GeneratedRecord: React.FC = () => {
const res = await getOAuthList({ page, page_size: pageSize });
const data = res?.data || res;
console.log(res);
setOauthList(data|| []);
setOauthList(data || []);
setOauthTotal(res.pagination.total || 0);
} catch (error) {
console.error('加载授权列表失败:', error);
@@ -678,7 +680,7 @@ const GeneratedRecord: React.FC = () => {
const handleStartBatchUpload = async () => {
setUploading(true);
const uploadProgressMap: { [key: string]: { status: 'pending' | 'uploading' | 'success' | 'error'; message: string } } = {};
for (const itemId of selectedItems) {
uploadProgressMap[itemId] = { status: 'pending', message: '' };
}
@@ -771,6 +773,17 @@ const GeneratedRecord: React.FC = () => {
}
};
// 日期选择器变化处理函数
const handleDateChange = (dateString: string) => {
console.log(dateString);
console.log('qweqwe');
setSelectedDate(dateString);
};
useEffect(() => {
setLoading(true);
let parameters = '';
@@ -779,27 +792,70 @@ const GeneratedRecord: React.FC = () => {
} else {
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);
}).catch((err) => {
if (Pagebreak.page === 1) {
setRecordList([]);
if (selectedDate) {
let parameters = ``;
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(() => {
setLoading(false);
});
}, [filterType, filterMedia, Pagebreak.page]);
gethistoryItems(parameters).then((res: any) => {
const data = Array.isArray(res) ? res : (res?.items || []);
let recordList = [{
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(() => {
setPreTestLoading(true);
@@ -878,7 +934,7 @@ const GeneratedRecord: React.FC = () => {
}, [filterType, filterMedia]);
return (
<div>
<div style={{ minHeight: 'calc(100vh - 90px)', background: '#ffffffff' , overflowY: 'auto'}} >
{/* Header */}
{/* <div style={{ marginBottom: 20 }}>
<Typography.Title level={3} style={{ margin: '0 0 4px', color: '#1a1a2e', fontWeight: 700 }}>
@@ -1047,6 +1103,23 @@ const GeneratedRecord: React.FC = () => {
>
</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>
</div>
@@ -1292,7 +1365,7 @@ const GeneratedRecord: React.FC = () => {
<Typography.Text strong style={{ fontSize: 14, color: '#475569', marginBottom: 8, display: 'block' }}>
</Typography.Text>
<Select
mode="multiple"
value={selectedOauthItems}
@@ -1301,12 +1374,12 @@ const GeneratedRecord: React.FC = () => {
const selectedAccounts = oauthList
.filter(item => value.includes(String(item.id)))
.map(item => ({ accountId: String(item.accountId), authStatus: 'authorized' as const }));
const textAccounts = accountIdInput.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0)
.map(id => ({ accountId: id, authStatus: 'pending' as const }));
const mergedAccounts = [...selectedAccounts, ...textAccounts];
const seen = new Set<string>();
const finalAccounts = mergedAccounts.filter(a => {
@@ -1314,7 +1387,7 @@ const GeneratedRecord: React.FC = () => {
seen.add(a.accountId);
return true;
});
setAccountIdList(finalAccounts);
}}
placeholder="点击选择授权账户"
@@ -1417,24 +1490,24 @@ const GeneratedRecord: React.FC = () => {
<Typography.Text strong style={{ fontSize: 14, color: '#475569', marginBottom: 8, display: 'block', marginTop: 16 }}>
ID
</Typography.Text>
<Input.TextArea
value={accountIdInput}
onChange={(e) => {
const value = e.target.value;
setAccountIdInput(value);
const ids = value.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0);
const uniqueIds = [...new Set(ids)];
const textAccounts = uniqueIds.map(id => ({ accountId: id, authStatus: 'pending' as const }));
const oauthAccounts = oauthList
.filter(item => selectedOauthItems.includes(String(item.id)))
.map(item => ({ accountId: String(item.accountId), authStatus: 'authorized' as const }));
const mergedAccounts = [...oauthAccounts, ...textAccounts];
const seen = new Set<string>();
const finalAccounts = mergedAccounts.filter(a => {
@@ -1442,7 +1515,7 @@ const GeneratedRecord: React.FC = () => {
seen.add(a.accountId);
return true;
});
setAccountIdList(finalAccounts);
}}
placeholder="粘贴账户ID,每行一个,例如:
@@ -1458,7 +1531,7 @@ const GeneratedRecord: React.FC = () => {
<Typography.Text style={{ fontSize: 13, color: '#64748b', marginBottom: 8, display: 'block' }}>
{accountIdList.length}
</Typography.Text>
<Table
dataSource={accountIdList.map((item, index) => ({ ...item, key: index }))}
columns={[
@@ -1493,7 +1566,7 @@ const GeneratedRecord: React.FC = () => {
borderRadius: 8,
}}
/>
<Button
type="text"
danger
+417 -276
View File
@@ -165,10 +165,10 @@ function InitialInfo() {
// 监听合并后的 steps 数据,判断第五步状态并启动/停止轮询
useEffect(() => {
// 检查第五步的状态(索引 4
const fifthStep = steps[4];
if (fifthStep && fifthStep.status !== 'completed' && fifthStep.status !== 'failed') {
// 第五步未完成,启动轮询
if (!pollingTimer) {
@@ -259,7 +259,7 @@ function InitialInfo() {
if (!creatID) {
return;
}
getReplicationDetail(creatID).then((res: any) => {
setTaskDetail(res);
if (res.steps) {
@@ -272,7 +272,7 @@ function InitialInfo() {
// 刷新任务详情(用于点击下一步后更新数据)
const refreshTaskDetail = () => {
if (!creatID) return;
getReplicationDetail(creatID).then((res: any) => {
setTaskDetail(res);
if (res.steps) {
@@ -294,7 +294,7 @@ function InitialInfo() {
refreshTaskDetail();
}).catch((error: any) => {
});
}
}
const newcreateimage = () => {
// console.log('下一步:', stepId);
let params = {
@@ -303,22 +303,22 @@ function InitialInfo() {
image_px: "2048x2048",
image_size: "2K"
}
gettwo(taskDetail.id, steps[1].id.toString(), params).then((res: any) => {
refreshTaskDetail();
}).catch((error: any) => {
});
}
}
// 下一步按钮点击处理
const handleNextStep = (stepId: number) => {
// 获取引擎 ID 和视频参数
const engineId = countType;
const videoParams = {
engine_id : countType,
engine_id: countType,
duration: videoDuration,
aspect_ratio: videoAspectRatio,
resolution: videoResolution,
@@ -333,12 +333,12 @@ function InitialInfo() {
// 这里可以添加下一步的逻辑,比如调用接口等
};
const createvideo = (stepId: number,engineId: string) => {
const createvideo = (stepId: number, engineId: string) => {
let params = {
engine_id: engineId,
}
getfour(taskDetail.id, stepId.toString(), params).then((res: any) => {
// 重新获取任务详情以更新数据
refreshTaskDetail();
@@ -349,8 +349,8 @@ function InitialInfo() {
let params = {
engine_id: steps[2].engineId,
}
getfour(taskDetail.id, steps[2].id.toString(), params).then((res: any) => {
// 重新获取任务详情以更新数据
refreshTaskDetail();
@@ -362,51 +362,112 @@ function InitialInfo() {
return (
<React.Fragment>
<div style={{ minHeight: 'calc(100vh - 90px)', background: '#f8fafc' }}>
<div style={{ height: '100%' }}>
<div
className="initialinfo-container"
style={{
margin: '-24px -32px -32px',
<div style={{ height: '100%', display: 'flex', justifyContent: 'space-between', gap: '2%' }}>
<div style={{ width: '70%', background: '#fff', borderRadius: 12, overflowY: 'auto', maxHeight: 'calc(100vh - 90px)' }}>
height: 'calc(100vh - 34px)',
// 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
type="text"
icon={<ArrowLeftOutlined />}
onClick={() => navigate(-1)}
style={{ color: '#64748b' }}
>
</Button>
<h3 style={{ margin: 0 }}>
<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>
<Button type="primary" ghost style={{ borderRadius: 6, padding: '4px 12px', fontSize: 12, borderColor: '#6366f1', color: '#6366f1' }} onClick={() => setIsModalOpen(true)}>
</Button>
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
</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 style={{ display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'flex-start' }}>
<div style={{ flex: 1, minWidth: 250 }}>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span>
<div className="medio_box" style={{ aspectRatio: '16/9', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<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 ? (
<video
controls
src={taskDetail.material.materialVideoUrl}
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
<video
controls
src={taskDetail.material.materialVideoUrl}
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 className="product_img" style={{ flex: 1, minWidth: 250 }}>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span>
<div className="medio_box" style={{ aspectRatio: '1/1', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<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 ? (
<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>
@@ -414,12 +475,15 @@ function InitialInfo() {
{taskDetail?.finalImageUrl && (
<div style={{ marginTop: 16 }}>
<div>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span>
<div className="medio_box" style={{ aspectRatio: '1/1', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
<img
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
alt=""
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<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
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
alt=""
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
/>
</div>
</div>
@@ -428,12 +492,15 @@ function InitialInfo() {
{taskDetail?.finalVideoUrl && (
<div style={{ marginTop: 16 }}>
<div>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span>
<div className="medio_box" style={{ aspectRatio: '16/9', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
<video
controls
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<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
controls
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
/>
</div>
</div>
@@ -441,9 +508,30 @@ function InitialInfo() {
)}
</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
defaultActiveKey={['']}
ghost
@@ -484,25 +572,25 @@ function InitialInfo() {
</div>
),
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: 原始素材 */}
{step.childId === 1 && (
<>
<div style={{ display: 'flex', gap: 20, marginBottom: 20, alignItems: 'flex-start', flexWrap: 'wrap' }}>
<div style={{ flex: 1, minWidth: 200 }}>
<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' }}>
<Text style={{ color: '#475569', fontSize: 13, marginBottom: 10, display: 'block', fontWeight: 500 }}></Text>
<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 ? (
<video
src={taskDetail.material.materialVideoUrl}
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
controls
<video
src={taskDetail.material.materialVideoUrl}
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
controls
/>
) : (
<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 }}>
<rect x="2" y="2" width="20" height="16" rx="2" ry="2"/>
<path d="M8 12l4 2 4-2"/>
<rect x="2" y="2" width="20" height="16" rx="2" ry="2" />
<path d="M8 12l4 2 4-2" />
</svg>
<div style={{ fontSize: 13 }}></div>
</div>
@@ -513,17 +601,17 @@ function InitialInfo() {
<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' }}>
{taskDetail?.material?.materialImageUrl ? (
<img
src={taskDetail.material.materialImageUrl}
alt="产品图片"
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
<img
src={taskDetail.material.materialImageUrl}
alt="产品图片"
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
/>
) : (
<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 }}>
<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"/>
<path d="M21 15l-5-5L5 21"/>
<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" />
<path d="M21 15l-5-5L5 21" />
</svg>
<div style={{ fontSize: 13 }}></div>
</div>
@@ -531,30 +619,33 @@ function InitialInfo() {
</div>
</div>
</div>
</>
)}
{/* 步骤2: 生成提示词 */}
{step.childId === 2 && (
<>
{/* <Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
{taskDetail?.generationPrompt || '暂无提示词'}
</Text> */}
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
{/* {taskDetail?.generationPrompt || '暂无提示词'} */}
{step?.output?.payload?.prompt || '暂无提示词'}
</Text>
<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 }}>
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
{step?.output?.payload?.prompt || '暂无提示词'}
</Text>
</div>
<Space style={{ marginTop: 16, gap: 12, width: '100%' }}>
<Button
type="default"
icon={<EditOutlined />}
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'}
>
</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>
</Space>
@@ -563,19 +654,39 @@ function InitialInfo() {
{/* 步骤3: 生成产品融合图 */}
{step.childId === 3 && (
<>
<div style={{ borderRadius: 8, overflow: 'hidden', marginBottom: 16, background: '#f1f5f9' }}>
{/* {step.output.result.result_image_url} */}
<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' }}>
{taskDetail.finalImageUrl ? (
<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>
)}
<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 style={{ marginBottom: 12 }}>
<div style={{ position: 'relative', display: 'inline-block' }}>
{/* 引擎选择器和视频参数设置 */}
<p style={{marginBottom:6,fontSize: 14, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}></p>
<div style={{ width:'100%', display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
<button
onClick={() => {
setShowEngineModal(!showEngineModal);
@@ -583,12 +694,12 @@ function InitialInfo() {
}}
className="image-settings-trigger"
style={{
minWidth: 200,
width: '100%',
padding: '4px 12px',
height: 34,
borderRadius: 8,
border: 'none',
backgroundColor: '#f1f5f9',
borderRadius: 10,
border: '1px solid rgba(99, 102, 241, 0.15)',
backgroundColor: 'rgba(255,255,255,0.7)',
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
@@ -596,7 +707,7 @@ function InitialInfo() {
transition: 'all 0.2s',
}}
>
<SettingOutlined style={{ fontSize: 14, color: '#64748b' }} />
<SettingOutlined style={{ fontSize: 14, color: '#6366f1' }} />
<Text style={{
fontSize: 14,
fontWeight: 500,
@@ -612,7 +723,7 @@ function InitialInfo() {
style={{
position: 'absolute',
bottom: 'calc(100% + 8px)',
left: -20,
left: -10,
width: 350,
backgroundColor: '#fff',
borderRadius: 16,
@@ -702,11 +813,9 @@ function InitialInfo() {
</div>
)}
</div>
</div>
{/* 视频参数设置 */}
<div style={{ marginBottom: 16 }}>
<div style={{ position: 'relative', display: 'inline-block' }}>
{/* 视频参数设置 */}
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
<button
onClick={() => {
setShowVideoSettingsModal(!showVideoSettingsModal);
@@ -714,12 +823,12 @@ function InitialInfo() {
}}
className="image-settings-trigger"
style={{
minWidth: 220,
width: '100%',
padding: '4px 12px',
height: 34,
borderRadius: 8,
border: 'none',
backgroundColor: '#f1f5f9',
borderRadius: 10,
border: '1px solid rgba(99, 102, 241, 0.15)',
backgroundColor: 'rgba(255,255,255,0.7)',
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
@@ -727,7 +836,7 @@ function InitialInfo() {
transition: 'all 0.2s',
}}
>
<LayoutOutlined style={{ fontSize: 14, color: '#64748b' }} />
<LayoutOutlined style={{ fontSize: 14, color: '#6366f1' }} />
<Text style={{
fontSize: 14,
fontWeight: 500,
@@ -743,7 +852,7 @@ function InitialInfo() {
style={{
position: 'absolute',
bottom: 'calc(100% + 8px)',
left: -20,
left: -160,
width: 350,
backgroundColor: '#fff',
borderRadius: 16,
@@ -935,41 +1044,40 @@ function InitialInfo() {
)}
</div>
</div>
<Space style={{ width: '100%', gap: 12 }}>
<Button type="default" icon={<EditOutlined />} style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }
} onClick={() => newcreateimage()} disabled={step.status !== 'completed'}>
</Button>
<Button
type="primary"
style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }}
onClick={() => { message.info('正在生成视频提示词,请稍候...'); handleNextStep(step.id); }}
disabled={step.status !== 'completed'}
>
</Button>
</Space>
<Button
type="primary"
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); }}
disabled={step.status !== 'completed'}
>
</Button>
</>
)}
{/* 步骤4: 生成视频提示词 */}
{step.childId === 4 && (
<>
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
{/* {taskDetail?.videoPrompt || '暂无视频提示词'} */}
{step?.output?.payload?.finalPrompt || '暂无提示词'}
</Text>
<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 }}>
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
{step?.output?.payload?.finalPrompt || '暂无提示词'}
</Text>
</div>
<Space style={{ marginTop: 16, gap: 12, width: '100%' }}>
<Button
type="default"
icon={<EditOutlined />}
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'}
>
</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>
</Space>
@@ -978,7 +1086,7 @@ function InitialInfo() {
{/* 步骤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 ? (
<video src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`} style={{ width: '100%', height: 180, objectFit: 'cover' }} controls />
) : step.status === 'processing' ? (
@@ -993,13 +1101,19 @@ function InitialInfo() {
)}
</div>
<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
type="primary"
icon={<DownloadOutlined />}
style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }}
<Button
type="primary"
icon={<DownloadOutlined />}
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'}
onClick={() => {
const videoUrl = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail?.finalVideoUrl}&download=1`;
@@ -1025,21 +1139,33 @@ function InitialInfo() {
</div>
</div>
<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}
onCancel={() => { if (!promptSaving) setModalVisible(false); }}
footer={[
<Button key="cancel" onClick={() => setModalVisible(false)} disabled={promptSaving}></Button>,
<Button key="confirm" type="primary" onClick={handleConfirm} loading={promptSaving} 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} style={{ borderRadius: 8, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none' }}></Button>,
]}
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' ? (
<TextArea
value={promptText}
onChange={(e) => setPromptText(e.target.value)}
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="请输入提示词"
/>
) : (
@@ -1050,171 +1176,186 @@ function InitialInfo() {
</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}
onCancel={() => setIsModalOpen(false)}
width={800}
width={850}
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 }}>
<Input
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>
</div>
{/* 表格 */}
<Table
columns={[
{
title: '产品名称',
dataIndex: 'targetProjectName',
key: 'targetProjectName',
align: 'center',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
align: 'center',
render: (text: string, record: any) => {
const status = text;
const currentStepCode = record.current_step_code || record.currentStepCode;
const getStatusText = () => {
columns={[
{
title: '产品名称',
dataIndex: 'targetProjectName',
key: 'targetProjectName',
align: 'center',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
align: 'center',
render: (text: string, record: any) => {
const status = text;
const currentStepCode = record.current_step_code || record.currentStepCode;
const getStatusText = () => {
if (currentStepCode === 'image_prompt_optimize') {
switch (status) {
case 'waiting_user':
return '等待融合图生成';
case 'processing':
return '图片提示词生成中';
case 'completed':
return '图片提示词生成成功';
case 'failed':
return '图片提示词生成失败';
default:
return status || '-';
}
switch (status) {
case 'waiting_user':
return { text: '等待融合图生成', color: '#f59e0b' };
case 'processing':
return { text: '图片提示词生成中', color: '#f59e0b' };
case 'completed':
return { text: '图片提示词生成成功', color: '#10b981' };
case 'failed':
return { text: '图片提示词生成失败', color: '#ef4444' };
default:
return { text: status || '-', color: '#666' };
}
} else if (currentStepCode === 'image_generate') {
switch (status) {
case 'waiting_user':
return '等待生成视频提示词';
case 'processing':
return '融合图生成中';
case 'completed':
return '融合图生成成功';
case 'failed':
return '融合图生成失败';
default:
return status || '-';
}
switch (status) {
case 'waiting_user':
return { text: '等待生成视频提示词', color: '#f59e0b' };
case 'processing':
return { text: '融合图生成中', color: '#f59e0b' };
case 'completed':
return { text: '融合图生成成功', color: '#10b981' };
case 'failed':
return { text: '融合图生成失败', color: '#ef4444' };
default:
return { text: status || '-', color: '#666' };
}
} else if (currentStepCode === 'video_prompt_optimize') {
switch (status) {
case 'waiting_user':
return '等待最终视频生成';
case 'processing':
return '视频提示词生成中';
case 'completed':
return '视频提示词生成成功';
case 'failed':
return '视频提示词生成失败';
default:
return status || '-';
}
switch (status) {
case 'waiting_user':
return { text: '等待最终视频生成', color: '#f59e0b' };
case 'processing':
return { text: '视频提示词生成中', color: '#f59e0b' };
case 'completed':
return { text: '视频提示词生成成功', color: '#10b981' };
case 'failed':
return { text: '视频提示词生成失败', color: '#ef4444' };
default:
return { text: status || '-', color: '#666' };
}
} else if (currentStepCode === 'video_generate') {
switch (status) {
case 'waiting_user':
return '';
case 'processing':
return '最终视频生成中';
case 'completed':
return '最终视频生成成功';
case 'failed':
return '最终视频生成失败';
default:
return status || '-';
}
switch (status) {
case 'waiting_user':
return { text: '', color: '#666' };
case 'processing':
return { text: '最终视频生成中', color: '#f59e0b' };
case 'completed':
return { text: '任务完成', color: '#10b981' };
case 'failed':
return { text: '最终视频生成失败', color: '#ef4444' };
default:
return { text: status || '-', color: '#666' };
}
} else if (currentStepCode === 'material_input') {
switch (status) {
case 'waiting_user':
return '等待生成图片提示词';
case 'processing':
return '素材处理中';
case 'completed':
return '素材上传成功';
case 'failed':
return '素材上传失败';
default:
return status || '-';
}
switch (status) {
case 'waiting_user':
return { text: '等待生成图片提示词', color: '#f59e0b' };
case 'processing':
return { text: '素材处理中', color: '#f59e0b' };
case 'completed':
return { text: '素材上传成功', color: '#10b981' };
case 'failed':
return { text: '素材上传失败', color: '#ef4444' };
default:
return { text: status || '-', color: '#666' };
}
} else {
const statusMap: Record<string, string> = {
'pending': '子任务待处理',
'waiting_user': '等待用户确认或触发',
'processing': '子任务处理中',
'completed': '子任务完成',
'failed': '子任务失败',
'cancelled': '子任务取消',
};
return statusMap[status] || status || '-';
const statusMap: Record<string, { text: string; color: string }> = {
'pending': { text: '子任务待处理', color: '#f59e0b' },
'waiting_user': { text: '等待用户确认或触发', color: '#f59e0b' },
'processing': { text: '子任务处理中', color: '#f59e0b' },
'completed': { text: '子任务完成', color: '#10b981' },
'failed': { text: '子任务失败', color: '#ef4444' },
'cancelled': { text: '子任务取消', color: '#94a3b8' },
};
return statusMap[status] || { text: status || '-', color: '#666' };
}
};
return getStatusText();
},
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
align: 'center',
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',
align: 'center',
render: (_, record) => (
<button
};
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>;
},
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
align: 'center',
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',
align: 'center',
render: (_, record) => (
<button
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>
),
},
]}
dataSource={tableData}
rowKey="id"
pagination={{
current: currentPage,
pageSize: pageSize,
total: total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
onChange: handlePageChange,
}}
style={{ fontSize: 13 }}
scroll={{ y: 350 }}
/>
</button>
),
},
]}
dataSource={tableData}
rowKey="id"
pagination={{
current: currentPage,
pageSize: pageSize,
total: total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
onChange: handlePageChange,
}}
style={{ fontSize: 13 }}
scroll={{ y: 350 }}
/>
</Modal>
</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 { 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 { createRemoveLens, getShotReplicationDetail, Removelist, removeCreate, splitCustom, uploadImage } from '../api';
import VideoTrimPicker from '../components/VideoTrimPicker';
@@ -23,6 +23,10 @@ function RemoveInfo() {
const [drawerVisible, setDrawerVisible] = useState(false);
const [trimModalVisible, setTrimModalVisible] = useState(false);
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 [productSellingPoint, setProductSellingPoint] = useState('');
const [productImage, setProductImage] = useState('');
@@ -31,6 +35,7 @@ function RemoveInfo() {
const [loading, setLoading] = useState(false);
const [splitLoading, setSplitLoading] = useState(false);
const [autoSplitLoading, setAutoSplitLoading] = useState(false);
const pollingIntervalRef = useRef<number | null>(null);
const videoUrl = useMemo(() => buildAssetUrl(taskDetail?.videoUrl), [taskDetail?.videoUrl]);
@@ -58,7 +63,69 @@ function RemoveInfo() {
if (!creatID) return;
try {
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 {
message.error('获取拆镜列表失败');
}
@@ -70,10 +137,19 @@ function RemoveInfo() {
useEffect(() => {
refreshPageData();
// 组件卸载时清除轮询
return () => {
if (pollingIntervalRef.current) {
clearInterval(pollingIntervalRef.current);
pollingIntervalRef.current = null;
}
};
}, [refreshPageData]);
const handleGenerate = (segmentId: string) => {
const handleGenerate = (segmentId: string, segmentName: string) => {
setCurrentSegment(segmentId);
setCurrentSegmentName(segmentName);
setDrawerVisible(true);
};
@@ -81,6 +157,7 @@ function RemoveInfo() {
setDrawerVisible(false);
setCurrentSegment(null);
setProductName('');
setCurrentSegmentName('');
setProductSellingPoint('');
setProductImage('');
};
@@ -218,13 +295,26 @@ function RemoveInfo() {
width: 150,
align: 'center' as const,
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 ? (
<video
controls
src={buildAssetUrl(record.segmentVideoUrl)}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
<>
<video
src={buildAssetUrl(record.segmentVideoUrl)}
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 }}>
{record.splitStatus === 'failed' ? '切割失败' : '切割中'}
@@ -238,15 +328,7 @@ function RemoveInfo() {
width: 500,
align: 'left' as const,
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建议的片段直接显示内容,不经过分析状态判断
if (record.source_mode === 'ai_suggestion' || record.sourceMode === 'ai_suggestion') {
@@ -296,91 +378,95 @@ function RemoveInfo() {
render: (text: string, record: any) => {
const status = text;
const currentStepCode = record.moduleProjectCurrentStepCode || record.currentStepCode;
console.log('currentStepCode',status, currentStepCode);
const getStatusText = () => {
if (currentStepCode === 'image_prompt_optimize') {
switch (status) {
case 'waiting_user':
return '等待融合图生成';
return { text: '等待融合图生成', color: '#f59e0b' };
case 'processing':
return '图片提示词生成中';
return { text: '图片提示词生成中', color: '#f59e0b' };
case 'completed':
return '图片提示词生成成功';
return { text: '图片提示词生成成功', color: '#10b981' };
case 'failed':
return '图片提示词生成失败';
return { text: '图片提示词生成失败', color: '#ef4444' };
default:
return status || '-';
return { text: status || '-', color: '#666' };
}
} else if (currentStepCode === 'image_generate') {
switch (status) {
case 'waiting_user':
return '等待生成视频提示词';
return { text: '等待生成视频提示词', color: '#f59e0b' };
case 'processing':
return '融合图生成中';
return { text: '融合图生成中', color: '#f59e0b' };
case 'completed':
return '融合图生成成功';
return { text: '融合图生成成功', color: '#10b981' };
case 'failed':
return '融合图生成失败';
return { text: '融合图生成失败', color: '#ef4444' };
default:
return status || '-';
}
} else if (currentStepCode === 'video_prompt_optimize') {
switch (status) {
case 'waiting_user':
return '等待最终视频生成';
return { text: '等待最终视频生成', color: '#f59e0b' };
case 'processing':
return '视频提示词生成中';
return { text: '视频提示词生成中', color: '#f59e0b' };
case 'completed':
return '视频提示词生成成功';
return { text: '视频提示词生成成功', color: '#10b981' };
case 'failed':
return '视频提示词生成失败';
return { text: '视频提示词生成失败', color: '#ef4444' };
default:
return status || '-';
return { text: status || '-', color: '#666' };
}
} else if (currentStepCode === 'video_generate') {
switch (status) {
case 'waiting_user':
return '';
return { text: '', color: '#666' };
case 'processing':
return '最终视频生成中';
return { text: '最终视频生成中', color: '#f59e0b' };
case 'completed':
return '最终视频生成成功';
return { text: '任务完成', color: '#10b981' };
case 'failed':
return '最终视频生成失败';
return { text: '最终视频生成失败', color: '#ef4444' };
default:
return status || '-';
return { text: status || '-', color: '#666' };
}
} else if (currentStepCode === 'material_input') {
switch (status) {
case 'waiting_user':
return '等待生成图片提示词';
return { text: '等待生成图片提示词', color: '#f59e0b' };
case 'processing':
return '素材处理中';
return { text: '素材处理中', color: '#f59e0b' };
case 'completed':
return '素材上传成功';
return { text: '素材上传成功', color: '#10b981' };
case 'failed':
return '素材上传失败';
return { text: '素材上传失败', color: '#ef4444' };
default:
return status || '-';
return { text: status || '-', color: '#666' };
}
} else {
if (!record.moduleProjectId) {
return '待生成任务';
return { text: '待生成任务', color: '#94a3b8' };
}
const statusMap: Record<string, string> = {
'pending': '子任务待处理',
'waiting_user': '等待用户确认或触发',
'processing': '子任务处理中',
'completed': '子任务完成',
'failed': '子任务失败',
'cancelled': '子任务取消',
const statusMap: Record<string, { text: string; color: string }> = {
'pending': { text: '子任务待处理', color: '#f59e0b' },
'waiting_user': { text: '等待用户确认或触发', color: '#f59e0b' },
'processing': { text: '子任务处理中', color: '#f59e0b' },
'completed': { text: '子任务完成', color: '#10b981' },
'failed': { text: '子任务失败', color: '#ef4444' },
'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
type="text"
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
type="text"
onClick={() => handleGenerate(String(record.id))}
onClick={() => handleGenerate(String(record.id), record.segmentName)}
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) ? '视频生成' : '待切割完成'}
</Button>
@@ -430,76 +516,124 @@ function RemoveInfo() {
<>
<div
style={{
minHeight: 'calc(100vh - 90px)',
margin: '-24px -32px -32px',
borderRadius: 20,
minHeight: 'calc(100vh - 32px)',
display: 'flex',
flexDirection: 'column',
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 }}>
<Button
type="text"
icon={<ArrowLeftOutlined />}
onClick={() => navigate(-1)}
style={{ fontSize: 16, color: '#666' }}
/>
{/* 背景装饰 */}
<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%' }} />
<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%' }} />
{/* 标题栏 */}
<div style={{ padding: '20px 32px', display: 'flex', alignItems: 'center', gap: 16, position: 'relative', zIndex: 10 }}>
<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>
{taskDetail ? (
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', overflow: 'hidden' }}>
<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 style={{ position: 'relative', width: 280, height: 160, borderRadius: 8, overflow: 'hidden', flexShrink: 0 }}>
<video
controls
src={videoUrl}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
</div>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', overflow: 'hidden', padding: '0 32px 32px' }}>
{/* 视频总结卡片 */}
<div
style={{
flex: 1,
background: 'linear-gradient(135deg, rgba(255,255,255,0.8) 0%, rgba(255,255,255,0.6) 100%)',
backdropFilter: 'blur(20px)',
borderRadius: 20,
display: 'flex',
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={{ position: 'relative', width: 280, height: 160, borderRadius: 12, overflow: 'hidden', flexShrink: 0, boxShadow: '0 4px 16px rgba(0,0,0,0.1)' }}>
<video
controls
src={videoUrl}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
</div>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}>
<div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<h2 style={{ fontSize: 20, fontWeight: 600, marginBottom: 16 }}></h2>
<div style={{ textAlign: 'right' }}>
<span style={{ color: '#999', fontSize: 12 }}>: {taskDetail.createdAt}</span>
</div>
</div>
<div style={{ display: 'flex', marginBottom: 12 }}>
<span style={{ color: '#999', fontSize: 14, marginRight: 12, width: 80, flexShrink: 0 }}>:</span>
<span style={{ color: '#333', fontWeight: 500 }}>{taskDetail.title}</span>
</div>
<div style={{ display: 'flex', marginBottom: 12 }}>
<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' ? (
<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 style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', position: 'relative', zIndex: 1 }}>
<div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<h2 style={{ fontSize: 20, fontWeight: 600, marginBottom: 16, background: 'linear-gradient(135deg, #1e293b 0%, #334155 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}></h2>
<div style={{ textAlign: 'right' }}>
<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>
</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 style={{ display: 'flex', gap: 16, marginBottom: 20, marginTop: 20 }}>
@@ -509,11 +643,23 @@ function RemoveInfo() {
disabled={!videoUrl || splitLoading}
style={{
flex: 1,
height: 48,
borderRadius: 8,
borderColor: '#6366f1',
height: 52,
borderRadius: 12,
border: '2px solid rgba(99, 102, 241, 0.3)',
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="取消"
onConfirm={handleAutoGenerate}
>
<div style={{ display: 'flex', alignItems: 'center', flex: 1 }}>
<Button
type="primary"
loading={autoSplitLoading}
disabled={autoSplitLoading}
style={{
flex: 1,
height: 48,
borderRadius: 8,
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
fontWeight: 500,
border: 'none',
}}
>
{autoSplitButtonText}
{taskDetail?.aiSuggestions && taskDetail.aiSuggestions.length > 0 && (
<Tooltip
title={
<div>
<div style={{ fontWeight: 600, marginBottom: 8, color: '#6366f1' }}>AI拆分方案</div>
{(() => {
console.log('ai_suggestions:', taskDetail?.ai_suggestions);
console.log('ai_suggestions2:', taskDetail?.aiSuggestions);
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>
}
placement="top"
trigger="hover"
overlayInnerStyle={{ maxWidth: 400, background: '#fff', border: '1px solid #e5e7eb', borderRadius: 12, padding: 12 }}
>
<div style={{ borderRadius: 8, background: 'rgba(99, 102, 241, 0.1)', color: '#ebebeb', fontSize: 13, fontWeight: 500, cursor: 'pointer' }}>
(AI )
<Button
type="primary"
loading={autoSplitLoading}
disabled={autoSplitLoading}
style={{
flex: 1,
height: 52,
borderRadius: 12,
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%)',
fontWeight: 600,
fontSize: 15,
border: 'none',
boxShadow: '0 4px 20px rgba(99, 102, 241, 0.3)',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
position: 'relative',
overflow: 'hidden',
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-2px)';
e.currentTarget.style.boxShadow = '0 6px 25px rgba(99, 102, 241, 0.4)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = '0 4px 20px rgba(99, 102, 241, 0.3)';
}}
>
<span style={{ position: 'relative', zIndex: 1 }}>{autoSplitButtonText}</span>
{/* 按钮光效 */}
<div style={{
position: 'absolute',
top: 0,
left: '-100%',
width: '100%',
height: '100%',
background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent)',
transition: 'left 0.5s ease'
}}
onMouseEnter={(e) => {
e.currentTarget.style.left = '100%';
}} />
{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>
</Tooltip>
}
placement="top"
trigger="hover"
>
<span style={{ marginLeft: 8, fontSize: 13, opacity: 0.9 }}>(AI )</span>
</Tooltip>
)}
</Button>
</div>
</Button>
</Popconfirm>
</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
columns={columns}
dataSource={tableData}
pagination={false}
bordered={false}
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>
@@ -607,15 +829,16 @@ function RemoveInfo() {
<Drawer
title={
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
<span>
<span style={{ color: '#6366f1' }}></span>
<span style={{ color: '#6366f1' }}>-{currentSegment}</span>
</span>
<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: 18, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}></span>
{currentSegmentName && <span style={{ fontSize: 14, color: '#64748b' }}>- {currentSegmentName}</span>}
</div>
<Button
type="text"
icon={<XOutlined />}
onClick={handleCloseDrawer}
style={{ padding: 0 }}
style={{ padding: 0, color: '#64748b', fontSize: 14 }}
/>
</div>
}
@@ -625,75 +848,88 @@ function RemoveInfo() {
open={drawerVisible}
size={480}
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>
<label style={{ fontWeight: 400, color: '#333', marginBottom: 12, display: 'block' }}>
<span style={{ color: '#ff4d4f' }}>*</span>
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
<div style={{ background: '#fff', borderRadius: 12, padding: 16, border: '1px solid rgba(99, 102, 241, 0.1)' }}>
<label style={{ fontWeight: 500, color: '#334155', marginBottom: 8, display: 'block', fontSize: 14 }}>
<span style={{ color: '#ef4444' }}>*</span>
</label>
<div style={{ display: 'flex', gap: 16 }}>
<div style={{ display: 'flex', gap: 12 }}>
<Upload
listType="picture-card"
onChange={handleProductImageChange}
beforeUpload={beforeUploadProductImage}
maxCount={1}
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 && (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}>
<PlusOutlined style={{ fontSize: 24, color: '#999' }} />
<span style={{ fontSize: 12, color: '#999' }}> *</span>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6 }}>
<PlusOutlined style={{ fontSize: 20, color: '#6366f1' }} />
<span style={{ fontSize: 12, color: '#64748b' }}></span>
</div>
)}
</Upload>
</div>
</div>
<div>
<label style={{ fontWeight: 500, color: '#333', marginBottom: 12, display: 'block' }}>
<div style={{ background: '#fff', borderRadius: 12, padding: 16, border: '1px solid rgba(99, 102, 241, 0.1)' }}>
<label style={{ fontWeight: 500, color: '#334155', marginBottom: 8, display: 'block', fontSize: 14 }}>
<span style={{ color: '#ef4444' }}>*</span>
</label>
<Input
value={productName}
onChange={(e) => setProductName(e.target.value)}
placeholder="请输入产品名称"
style={{ height: 48, borderRadius: 8 }}
style={{ height: 44, borderRadius: 8, border: '1px solid rgba(99, 102, 241, 0.2)' }}
maxLength={10}
showCount
/>
</div>
<div>
<label style={{ fontWeight: 500, color: '#333', marginBottom: 12, display: 'block' }}>
<div style={{ background: '#fff', borderRadius: 12, padding: 16, border: '1px solid rgba(99, 102, 241, 0.1)' }}>
<label style={{ fontWeight: 500, color: '#334155', marginBottom: 8, display: 'block', fontSize: 14 }}>
<span style={{ color: '#ef4444' }}>*</span>
</label>
<TextArea
value={productSellingPoint}
onChange={(e) => setProductSellingPoint(e.target.value)}
placeholder="请输入产品卖点"
style={{ borderRadius: 8 }}
style={{ borderRadius: 8, border: '1px solid rgba(99, 102, 241, 0.2)' }}
maxLength={100}
showCount
rows={3}
/>
</div>
<div style={{ display: 'flex', gap: 16, marginTop: 24 }}>
<div style={{ display: 'flex', gap: 12, marginTop: 8 }}>
<Button
type="default"
type="primary"
onClick={handleManualGenerate}
loading={loading}
disabled={loading}
style={{
flex: 1,
height: 48,
borderRadius: 8,
borderColor: '#6366f1',
color: '#6366f1',
fontWeight: 500,
borderRadius: 10,
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
fontWeight: 600,
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 ? '生成中...' : '手动生成'}
@@ -701,6 +937,42 @@ function RemoveInfo() {
</div>
</div>
</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);
message.success('任务创建成功');
// 清空上传内容和输入框
cleanupResources();
} catch (err) {
message.error('任务创建失败,请重试');
@@ -108,194 +107,194 @@ export default function VideoFrameExtractor() {
fetchList(page, size);
};
// 打开弹窗时获取列表数据
const handleOpenModal = () => {
setIsModalOpen(true);
fetchList(1, 10);
};
return (
<div style={{ minHeight: 'calc(100vh - 90px)', overflow: 'auto', background: 'linear-gradient(135deg, #f5f3ff 0%, #fdf2f8 100%)', padding: '20px' }}>
<div style={{ maxWidth: 1200, margin: '0 auto' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 24 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div>
<div style={{
width: 36,
height: 36,
background: 'linear-gradient(135deg, #6366f1 0%, #ec4899 100%)',
borderRadius: 10,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
fontSize: 18,
fontWeight: 600
}}>
</div>
</div>
<div
style={{
margin: '-24px -32px -32px',
borderRadius: 20,
minHeight: 'calc(100vh - 32px)',
overflow: 'auto',
background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 50%, #f0f9ff 100%)',
position: 'relative',
padding: '20px 32px',
}}
>
<div style={{ maxWidth: 1200, margin: '50px auto', position: 'relative', zIndex: 10 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 32 }}>
<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>
<h1 style={{
fontSize: 22,
fontWeight: 600,
fontSize: 24,
fontWeight: 700,
margin: 0,
color: '#1e293b'
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text'
}}>
AI视频拆镜工作台
</h1>
<p style={{ fontSize: 13, color: '#94a3b8', margin: 4 }}>
<p style={{ fontSize: 13, color: '#64748b', margin: '4px 0 0 0' }}>
仿
</p>
</div>
<div style={{ width: 40, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
</div>
<button
<Button
type="text"
icon={<FileTextOutlined />}
onClick={handleOpenModal}
style={{
padding: '10px 20px',
background: 'white',
border: '1px solid #e2e8f0',
borderRadius: 8,
cursor: 'pointer',
fontSize: 14,
color: '#6366f1',
background: 'rgba(99, 102, 241, 0.1)',
borderRadius: 12,
padding: '10px 20px',
display: 'flex',
alignItems: 'center',
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>
</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>
</Button>
</div>
<div style={{ width: '100%', maxWidth: 720, margin: '0 auto' }}>
{error && (
<div style={{
backgroundColor: '#fff5f5',
border: '1px solid #ffccc7',
borderRadius: 8,
padding: '12px 16px',
marginBottom: 20,
color: '#d93026',
background: 'linear-gradient(135deg, rgba(239, 68, 68, 0.1) 0%, rgba(239, 68, 68, 0.05) 100%)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(239, 68, 68, 0.2)',
borderRadius: 12,
padding: '14px 20px',
marginBottom: 24,
color: '#ef4444',
fontSize: 14,
textAlign: 'center'
textAlign: 'center',
fontWeight: 500,
}}>
{error}
</div>
)}
{!videoUrl && (
<Upload.Dragger
accept="video/*"
beforeUpload={handleFileChange}
showUploadList={false}
disabled={loading}
<div
style={{
background: loading ? '#f3f4f6' : '#faf5ff',
border: `2px dashed ${loading ? '#d1d5db' : '#c4b5fd'}`,
background: 'linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0.85) 100%)',
backdropFilter: 'blur(20px)',
borderRadius: 24,
padding: '60px 20px',
cursor: loading ? 'not-allowed' : 'pointer',
border: '1px solid rgba(99, 102, 241, 0.1)',
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
position: 'relative',
overflow: 'hidden',
}}
>
{loading ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div style={{
width: 100,
height: 100,
margin: '0 auto 20px',
background: '#e5e7eb',
borderRadius: 24,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<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%' }} />
<Upload.Dragger
accept="video/*"
beforeUpload={handleFileChange}
showUploadList={false}
disabled={loading}
style={{
background: loading ? 'rgba(243, 244, 246, 0.3)' : 'transparent',
border: `2px dashed ${loading ? 'rgba(209, 213, 219, 0.5)' : 'rgba(99, 102, 241, 0.3)'}`,
borderRadius: 24,
padding: '56px 20px',
cursor: loading ? 'not-allowed' : 'pointer',
margin: 0,
}}
>
{loading ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div style={{
width: 40,
height: 40,
border: '4px solid #6366f1',
borderTopColor: 'transparent',
borderRadius: '50%',
animation: 'spin 1s linear infinite'
}} />
width: 100,
height: 100,
margin: '0 auto 20px',
background: 'linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(139, 92, 246, 0.1) 100%)',
borderRadius: 24,
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>
<p style={{ fontSize: 16, color: '#6366f1', margin: '0 0 8px 0', fontWeight: 500 }}>
...
</p>
<p style={{ fontSize: 13, color: '#999', margin: 0 }}>
</p>
</div>
) : (
<>
<div style={{
width: 100,
height: 100,
margin: '0 auto 20px',
background: 'linear-gradient(135deg, #e0e7ff 0%, #fce7f3 100%)',
borderRadius: 24,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<CloudUploadOutlined style={{ fontSize: 40, color: '#818cf8' }} />
</div>
<p style={{ fontSize: 16, color: '#333', margin: '0 0 8px 0', fontWeight: 500 }}>
</p>
<p style={{ fontSize: 13, color: '#999', margin: 0 }}>
MP4MOV 100MB 3
</p>
</>
)}
</Upload.Dragger>
) : (
<>
<div style={{
width: 100,
height: 100,
margin: '0 auto 20px',
background: 'linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(139, 92, 246, 0.1) 100%)',
borderRadius: 24,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<CloudUploadOutlined style={{ fontSize: 40, color: '#6366f1' }} />
</div>
<p style={{ fontSize: 16, color: '#1e293b', margin: '0 0 8px 0', fontWeight: 600 }}>
</p>
<p style={{ fontSize: 13, color: '#64748b', margin: 0 }}>
MP4MOV 100MB 3
</p>
</>
)}
</Upload.Dragger>
</div>
)}
{videoUrl && (
<div style={{
position: 'relative',
borderRadius: 24,
padding: 4,
background: 'linear-gradient(135deg, #6366f1 0%, #ec4899 100%)'
}}>
<div
style={{
background: 'linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0.85) 100%)',
backdropFilter: 'blur(20px)',
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={{
borderRadius: 22,
background: '#ffffff',
padding: '24px'
padding: '24px',
position: 'relative',
zIndex: 1,
}}>
<Popconfirm
title="确定要删除吗?"
@@ -310,16 +309,27 @@ export default function VideoFrameExtractor() {
right: 20,
width: 36,
height: 36,
border: '1px solid #e5e7eb',
background: '#fff',
border: '1px solid rgba(99, 102, 241, 0.2)',
background: 'rgba(99, 102, 241, 0.05)',
borderRadius: 10,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 16,
color: '#666',
zIndex: 10
color: '#6366f1',
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%',
maxHeight: 320,
borderRadius: 12,
background: '#000'
background: '#000',
boxShadow: '0 4px 16px rgba(0,0,0,0.1)'
}}
/>
</div>
{/* 产品名称输入框 */}
<div style={{ marginBottom: 24 }}>
<label style={{
display: 'block',
fontSize: 14,
fontWeight: 500,
color: '#374151',
color: '#334155',
marginBottom: 8
}}>
<span style={{ color: '#ef4444' }}>*</span>
</label>
<Input
placeholder="请输入产品名称"
@@ -361,9 +371,16 @@ export default function VideoFrameExtractor() {
onChange={(e) => setProductName(e.target.value)}
style={{
width: '100%',
height: 40,
borderRadius: 8,
borderColor: '#e5e7eb'
height: 44,
borderRadius: 10,
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>
@@ -375,28 +392,34 @@ export default function VideoFrameExtractor() {
style={{
padding: '14px 56px',
background: videoUrl && productName.trim() && !loading
? 'linear-gradient(135deg, #6366f1 0%, #ec4899 100%)'
? 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%)'
: '#e2e8f0',
color: 'white',
border: 'none',
borderRadius: 14,
borderRadius: 12,
fontSize: 15,
fontWeight: 600,
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',
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 style={{
fontSize: 12,
opacity: 0.85,
fontWeight: 400
}}>
{/* 预计消耗 10 个积分 */}
</span>
</button>
</div>
</div>
@@ -406,91 +429,168 @@ export default function VideoFrameExtractor() {
<canvas ref={canvasRef} style={{ display: 'none' }} />
<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}
onCancel={() => setIsModalOpen(false)}
width={800}
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
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>
</div>
<Table
columns={[
// {
// title: '产品图片',
// dataIndex: 'image',
// key: 'image',
// width: 90,
// render: (image: string) => (
// <img
// src={image}
// alt="产品图片"
// style={{ width: 50, height: 50, objectFit: 'cover', borderRadius: 6 }}
// />
// ),
// },
{
title: '产品名称',
dataIndex: 'title',
key: 'title',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
},
{
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={() => navigate(`/removelens/${record.id}/removeinfo`)}
style={{ color: '#6366f1', textDecoration: 'none', fontSize: 13, 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 }}
scroll={{ y: 350 }}
/>
<div
style={{
background: 'linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.7) 100%)',
backdropFilter: 'blur(20px)',
borderRadius: 16,
overflow: 'hidden',
border: '1px solid rgba(99, 102, 241, 0.1)',
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
}}
>
<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>
</div>
</div>
+401 -219
View File
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { Button, Typography, Collapse, Space, Modal, Input, Table, message } from 'antd';
import { ArrowLeftOutlined, PlayCircleOutlined, CheckCircleOutlined, EditOutlined, DownloadOutlined, SettingOutlined, LayoutOutlined } from '@ant-design/icons';
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 './css/InitialInfo.css';
@@ -150,7 +150,7 @@ function InitialInfo() {
useEffect(() => {
console.log('creatID', creatID);
if (creatID) {
removeDetail(creatID).then((res: any) => {
setTaskDetail(res);
@@ -164,10 +164,10 @@ function InitialInfo() {
// 监听合并后的 steps 数据,判断第五步状态并启动/停止轮询
useEffect(() => {
// 检查第五步的状态(索引 4
const fifthStep = steps[4];
if (fifthStep && fifthStep.status !== 'completed' && fifthStep.status !== 'failed') {
// 第五步未完成,启动轮询
if (!pollingTimer) {
@@ -189,7 +189,7 @@ function InitialInfo() {
const handleOpenModal = (prompt?: any, type?: string, stepId?: string | number) => {
setCurrentType(type || 'image');
setEditingPromptStepId(stepId ? String(stepId) : '');
if (type === 'video' && typeof prompt === 'object') {
setFormData(prompt);
setPromptText('');
@@ -197,7 +197,7 @@ function InitialInfo() {
setPromptText(prompt || '');
setFormData({});
}
setModalVisible(true);
};
@@ -258,7 +258,7 @@ function InitialInfo() {
if (!creatID) {
return;
}
removeDetail(creatID).then((res: any) => {
setTaskDetail(res);
if (res.steps) {
@@ -271,7 +271,7 @@ function InitialInfo() {
// 刷新任务详情(用于点击下一步后更新数据)
const refreshTaskDetail = () => {
if (!creatID) return;
removeDetail(creatID).then((res: any) => {
setTaskDetail(res);
if (res.steps) {
@@ -281,13 +281,13 @@ function InitialInfo() {
});
};
const createone = (stepId: number) => {
removeone(taskDetail.id, stepId.toString()).then((res: any) => {
// 重新获取任务详情以更新数据
refreshTaskDetail();
}).catch((error: any) => {
});
}
}
const createimage = (stepId: number) => {
let params = {
@@ -301,7 +301,7 @@ function InitialInfo() {
refreshTaskDetail();
}).catch((error: any) => {
});
}
}
const newcreateimage = () => {
// console.log('下一步:', stepId);
let params = {
@@ -310,22 +310,22 @@ function InitialInfo() {
image_px: "2048x2048",
image_size: "2K"
}
removetwo(taskDetail.id, steps[1].id.toString(), params).then((res: any) => {
refreshTaskDetail();
}).catch((error: any) => {
});
}
}
// 下一步按钮点击处理
const handleNextStep = (stepId: number) => {
// 获取引擎 ID 和视频参数
const engineId = countType;
const videoParams = {
engine_id : countType,
engine_id: countType,
duration: videoDuration,
aspect_ratio: videoAspectRatio,
resolution: videoResolution,
@@ -340,12 +340,12 @@ function InitialInfo() {
// 这里可以添加下一步的逻辑,比如调用接口等
};
const createvideo = (stepId: number,engineId: string) => {
const createvideo = (stepId: number, engineId: string) => {
let params = {
engine_id: engineId,
}
removefour(taskDetail.id, stepId.toString(), params).then((res: any) => {
// 重新获取任务详情以更新数据
refreshTaskDetail();
@@ -356,8 +356,8 @@ function InitialInfo() {
let params = {
engine_id: steps[2].engineId,
}
removefour(taskDetail.id, steps[2].id.toString(), params).then((res: any) => {
// 重新获取任务详情以更新数据
refreshTaskDetail();
@@ -369,76 +369,141 @@ function InitialInfo() {
return (
<React.Fragment>
<div style={{ minHeight: 'calc(100vh - 90px)', background: '#f8fafc' }}>
<div style={{ height: '100%' }}>
<div style={{ height: '100%', display: 'flex', justifyContent: 'space-between', gap: '2%' }}>
<div style={{ width: '70%', background: '#fff', borderRadius: 12, overflowY: 'auto', maxHeight: 'calc(100vh - 90px)' }}>
<div style={{ borderBottom: '1px solid #e2e8f0', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px', boxSizing: 'border-box' }}>
<Button
type="text"
icon={<ArrowLeftOutlined />}
onClick={() => navigate(-1)}
style={{ color: '#64748b' }}
>
</Button>
<h3 style={{ margin: 0 }}>
<div
className="initialinfo-container"
style={{
margin: '-24px -32px -32px',
height: 'calc(100vh - 34px)',
position: 'relative',
boxSizing: 'border-box',
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
}}
>
{/* 背景装饰 */}
<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>
<Button type="primary" ghost style={{ borderRadius: 6, padding: '4px 12px', fontSize: 12, borderColor: '#6366f1', color: '#6366f1' }} onClick={() => setIsModalOpen(true)}>
</Button>
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
</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 style={{ display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'flex-start' }}>
<div style={{ flex: 1, minWidth: 250 }}>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span>
<div className="medio_box" style={{ aspectRatio: '16/9', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<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 ? (
<video
controls
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.material.materialVideoUrl}`}
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
<video
controls
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.material.materialVideoUrl}`}
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 className="product_img" style={{ flex: 1, minWidth: 250 }}>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span>
<div className="medio_box" style={{ aspectRatio: '1/1', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<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 ? (
<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>
{taskDetail?.finalImageUrl && (
<div style={{ marginTop: 16 }}>
<div style={{ marginTop: 24 }}>
<div>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span>
<div className="medio_box" style={{ aspectRatio: '1/1', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
<img
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
alt=""
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<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
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
alt=""
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
/>
</div>
</div>
</div>
)}
{taskDetail?.finalVideoUrl && (
<div style={{ marginTop: 16 }}>
<div style={{ marginTop: 24 }}>
<div>
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}></span>
<div className="medio_box" style={{ aspectRatio: '16/9', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
<video
controls
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
<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
controls
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
/>
</div>
</div>
@@ -446,8 +511,28 @@ function InitialInfo() {
)}
</div>
</div>
<div style={{ width: '28%', minWidth: '400px', background: '#fff', borderRadius: 12, overflowY: 'auto', maxHeight: 'calc(100vh - 90px)' }}>
<div style={{ padding: '14px' }}> </div>
</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
defaultActiveKey={['']}
ghost
@@ -488,25 +573,25 @@ function InitialInfo() {
</div>
),
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: 原始素材 */}
{step.childId === 1 && (
<>
<div style={{ display: 'flex', gap: 20, marginBottom: 20, alignItems: 'flex-start', flexWrap: 'wrap' }}>
<div style={{ flex: 1, minWidth: 200 }}>
<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 ? (
<video
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.material.materialVideoUrl}`}
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
controls
<video
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.material.materialVideoUrl}`}
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
controls
/>
) : (
<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 }}>
<rect x="2" y="2" width="20" height="16" rx="2" ry="2"/>
<path d="M8 12l4 2 4-2"/>
<rect x="2" y="2" width="20" height="16" rx="2" ry="2" />
<path d="M8 12l4 2 4-2" />
</svg>
<div style={{ fontSize: 13 }}></div>
</div>
@@ -515,53 +600,51 @@ function InitialInfo() {
</div>
<div style={{ flex: 1, minWidth: 200 }}>
<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 ? (
<img
src={taskDetail.material.materialImageUrl}
alt="产品图片"
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
<img
src={taskDetail.material.materialImageUrl}
alt="产品图片"
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
/>
) : (
<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 }}>
<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"/>
<path d="M21 15l-5-5L5 21"/>
<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" />
<path d="M21 15l-5-5L5 21" />
</svg>
<div style={{ fontSize: 13 }}></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>
</div>
</>
)}
{/* 步骤2: 生成提示词 */}
{step.childId === 2 && (
<>
{/* <Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
{taskDetail?.generationPrompt || '暂无提示词'}
</Text> */}
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
{/* {taskDetail?.generationPrompt || '暂无提示词'} */}
{step?.output?.payload?.prompt || '暂无提示词'}
</Text>
<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 }}>
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
{step?.output?.payload?.prompt || '暂无提示词'}
</Text>
</div>
<Space style={{ marginTop: 16, gap: 12, width: '100%' }}>
<Button
type="default"
icon={<EditOutlined />}
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'}
>
</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>
</Space>
@@ -570,19 +653,39 @@ function InitialInfo() {
{/* 步骤3: 生成产品融合图 */}
{step.childId === 3 && (
<>
<div style={{ borderRadius: 8, overflow: 'hidden', marginBottom: 16, background: '#f1f5f9' }}>
{/* {step.output.result.result_image_url} */}
<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' }}>
{taskDetail.finalImageUrl ? (
<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>
)}
<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 style={{ marginBottom: 12 }}>
<div style={{ position: 'relative', display: 'inline-block' }}>
{/* 引擎选择器和视频参数设置 */}
<p style={{ marginBottom: 6, fontSize: 14, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}></p>
<div style={{ width: '100%', display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
<button
onClick={() => {
setShowEngineModal(!showEngineModal);
@@ -590,12 +693,12 @@ function InitialInfo() {
}}
className="image-settings-trigger"
style={{
minWidth: 200,
width: '100%',
padding: '4px 12px',
height: 34,
borderRadius: 8,
border: 'none',
backgroundColor: '#f1f5f9',
borderRadius: 10,
border: '1px solid rgba(99, 102, 241, 0.15)',
backgroundColor: 'rgba(255,255,255,0.7)',
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
@@ -603,7 +706,7 @@ function InitialInfo() {
transition: 'all 0.2s',
}}
>
<SettingOutlined style={{ fontSize: 14, color: '#64748b' }} />
<SettingOutlined style={{ fontSize: 14, color: '#6366f1' }} />
<Text style={{
fontSize: 14,
fontWeight: 500,
@@ -619,7 +722,7 @@ function InitialInfo() {
style={{
position: 'absolute',
bottom: 'calc(100% + 8px)',
left: -20,
left: -10,
width: 350,
backgroundColor: '#fff',
borderRadius: 16,
@@ -709,11 +812,9 @@ function InitialInfo() {
</div>
)}
</div>
</div>
{/* 视频参数设置 */}
<div style={{ marginBottom: 16 }}>
<div style={{ position: 'relative', display: 'inline-block' }}>
{/* 视频参数设置 */}
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
<button
onClick={() => {
setShowVideoSettingsModal(!showVideoSettingsModal);
@@ -721,12 +822,12 @@ function InitialInfo() {
}}
className="image-settings-trigger"
style={{
minWidth: 220,
width: '100%',
padding: '4px 12px',
height: 34,
borderRadius: 8,
border: 'none',
backgroundColor: '#f1f5f9',
borderRadius: 10,
border: '1px solid rgba(99, 102, 241, 0.15)',
backgroundColor: 'rgba(255,255,255,0.7)',
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
@@ -734,7 +835,7 @@ function InitialInfo() {
transition: 'all 0.2s',
}}
>
<LayoutOutlined style={{ fontSize: 14, color: '#64748b' }} />
<LayoutOutlined style={{ fontSize: 14, color: '#6366f1' }} />
<Text style={{
fontSize: 14,
fontWeight: 500,
@@ -750,7 +851,7 @@ function InitialInfo() {
style={{
position: 'absolute',
bottom: 'calc(100% + 8px)',
left: -20,
left: -160,
width: 350,
backgroundColor: '#fff',
borderRadius: 16,
@@ -942,41 +1043,35 @@ function InitialInfo() {
)}
</div>
</div>
<Space style={{ width: '100%', gap: 12 }}>
<Button type="default" icon={<EditOutlined />} style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }
} onClick={() => newcreateimage()} disabled={step.status !== 'completed'}>
</Button>
<Button
type="primary"
style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }}
onClick={() => { message.info('正在生成视频提示词,请稍候...'); handleNextStep(step.id); }}
disabled={step.status !== 'completed'}
>
</Button>
</Space>
<Button
type="primary"
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); }}
disabled={step.status !== 'completed'}
>
</Button>
</>
)}
{/* 步骤4: 生成视频提示词 */}
{step.childId === 4 && (
<>
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
{/* {taskDetail?.videoPrompt || '暂无视频提示词'} */}
{step?.output?.payload?.finalPrompt || '暂无提示词'}
</Text>
<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 }}>
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
{step?.output?.payload?.finalPrompt || '暂无提示词'}
</Text>
</div>
<Space style={{ marginTop: 16, gap: 12, width: '100%' }}>
<Button
type="default"
icon={<EditOutlined />}
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'}
>
</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>
</Space>
@@ -985,7 +1080,7 @@ function InitialInfo() {
{/* 步骤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 ? (
<video src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`} style={{ width: '100%', height: 180, objectFit: 'cover' }} controls />
) : step.status === 'processing' ? (
@@ -1000,13 +1095,13 @@ function InitialInfo() {
)}
</div>
<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
type="primary"
icon={<DownloadOutlined />}
style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }}
<Button
type="primary"
icon={<DownloadOutlined />}
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'}
onClick={() => {
const videoUrl = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail?.finalVideoUrl}&download=1`;
@@ -1032,21 +1127,33 @@ function InitialInfo() {
</div>
</div>
<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}
onCancel={() => setModalVisible(false)}
onCancel={() => { if (!promptSaving) setModalVisible(false); }}
footer={[
<Button key="cancel" onClick={() => setModalVisible(false)}></Button>,
<Button key="confirm" type="primary" onClick={handleConfirm} loading={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} style={{ borderRadius: 8, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none' }}></Button>,
]}
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' ? (
<TextArea
value={promptText}
onChange={(e) => setPromptText(e.target.value)}
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="请输入提示词"
/>
) : (
@@ -1057,93 +1164,168 @@ function InitialInfo() {
</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}
onCancel={() => setIsModalOpen(false)}
width={800}
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 }}>
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 16, gap: 8 }}>
<Input
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>
</div>
{/* 表格 */}
<Table
columns={[
{
title: '产品名称',
dataIndex: 'targetProjectName',
key: 'targetProjectName',
},
{
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,
<div
style={{
background: 'linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.7) 100%)',
backdropFilter: 'blur(20px)',
borderRadius: 16,
overflow: 'hidden',
border: '1px solid rgba(99, 102, 241, 0.1)',
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
}}
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>
</React.Fragment>
);
+95 -5
View File
@@ -1,26 +1,116 @@
*{
* {
box-sizing: border-box;
}
.material_box>div{
margin: 20px 10px;
.material_box {
padding: 24px;
}
.material_box>div {
margin: 0 0 20px 0;
}
.medio_box {
width: 280px;
height: 150px;
margin-top: 6px;
}
.medio_box img ,.medio_box video {
.medio_box img,
.medio_box video {
width: 100%;
height: 100%;
}
/* 加载旋转动画 */
@keyframes spinSlow {
from {
transform: rotate(0deg);
}
to {
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);
}