Merge branch 'dev'
This commit is contained in:
@@ -379,6 +379,11 @@ export async function getone(projectId: string, stepId: string): Promise<any> {
|
||||
return api.post(`/hot-opening-replications/tasks/${projectId}/steps/${stepId}/generate-image-prompt`);
|
||||
}
|
||||
|
||||
// 修改图片生成提示词
|
||||
export async function updateImagePrompt(projectId: string, stepId: string, params: any): Promise<any> {
|
||||
return api.put(`/hot-opening-replications/tasks/${projectId}/steps/${stepId}/image-prompt`, params);
|
||||
}
|
||||
|
||||
// 第二步,生成图片
|
||||
export async function gettwo(projectId: string, stepId: string ,params: any): Promise<any> {
|
||||
return api.post(`/hot-opening-replications/tasks/${projectId}/steps/${stepId}/generate-image`, params);
|
||||
@@ -534,6 +539,16 @@ export async function removeone(projectId: string, stepId: string): Promise<any>
|
||||
|
||||
}
|
||||
|
||||
// 修改图片提示词
|
||||
export async function updateShotImagePrompt(projectId: string, stepId: string, params: any): Promise<any> {
|
||||
return api.put(`/shot-replications/projects/${projectId}/steps/${stepId}/image-prompt`, params);
|
||||
}
|
||||
|
||||
// 修改视频提示词
|
||||
export async function updateShotVideoPromptSchema(projectId: string, stepId: string, params: any): Promise<any> {
|
||||
return api.put(`/shot-replications/projects/${projectId}/steps/${stepId}/video-prompt-schema`, params);
|
||||
}
|
||||
|
||||
// 第二步,生成图片
|
||||
export async function removetwo(projectId: string, stepId: string ,params: any): Promise<any> {
|
||||
return api.post(`/shot-replications/projects/${projectId}/steps/${stepId}/generate-image`, params);
|
||||
|
||||
@@ -20,6 +20,12 @@ const LOCKED_TOP_LEVEL_KEYS = new Set([
|
||||
'质量控制',
|
||||
]);
|
||||
|
||||
const HIDDEN_TOP_LEVEL_KEYS = new Set([
|
||||
'schemaUsage',
|
||||
'schemaVersion',
|
||||
'输出规格限制',
|
||||
]);
|
||||
|
||||
const LOCKED_FRAME_KEYS = new Set([
|
||||
'视频时长',
|
||||
'视频比例',
|
||||
@@ -174,7 +180,7 @@ const VideoPromptSchemaEditor: React.FC<VideoPromptSchemaEditorProps> = ({ value
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
|
||||
<Space>
|
||||
<Text strong style={{ color: '#334155', fontSize: 13 }}>{fieldTitle(key)}</Text>
|
||||
{!editableArray && <Tag color="default">锁定结构</Tag>}
|
||||
{/* {!editableArray && <Tag color="default">锁定结构</Tag>} */}
|
||||
</Space>
|
||||
{editableArray && (
|
||||
<Button size="small" type="link" onClick={() => onChange(addArrayItem(safeValue, path, sample))}>
|
||||
@@ -211,11 +217,11 @@ const VideoPromptSchemaEditor: React.FC<VideoPromptSchemaEditorProps> = ({ value
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(rootKey === '动作流程' || rootKey === '镜头流程') && (
|
||||
{/* {(rootKey === '动作流程' || rootKey === '镜头流程') && (
|
||||
<Text style={{ display: 'block', marginTop: 6, color: '#94a3b8', fontSize: 12 }}>
|
||||
时间段和条目数量由后端锁定,只允许编辑每段里的动作、镜头、说明等内容。
|
||||
</Text>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -226,7 +232,7 @@ const VideoPromptSchemaEditor: React.FC<VideoPromptSchemaEditorProps> = ({ value
|
||||
<div key={path.join('.')} style={{ marginBottom: 18 }}>
|
||||
<Space style={{ marginBottom: 8 }}>
|
||||
<Text strong style={{ color: '#334155', fontSize: 13 }}>{fieldTitle(key)}</Text>
|
||||
{lockedSection && <Tag color="default">只读</Tag>}
|
||||
{/* {lockedSection && <Tag color="default">只读</Tag>} */}
|
||||
</Space>
|
||||
<div
|
||||
style={{
|
||||
@@ -246,10 +252,11 @@ const VideoPromptSchemaEditor: React.FC<VideoPromptSchemaEditorProps> = ({ value
|
||||
<div key={path.join('.')} style={{ marginBottom: 12 }}>
|
||||
<Space style={{ marginBottom: 4 }}>
|
||||
<Text style={{ color: '#64748b', fontSize: 12 }}>{fieldTitle(key)}</Text>
|
||||
{locked && <Tag color="default">只读</Tag>}
|
||||
{/* {locked && <Tag color="default">只读12312</Tag>} */}
|
||||
</Space>
|
||||
{locked ? (
|
||||
<ReadonlyBlock value={nodeValue} />
|
||||
// <></>
|
||||
) : (
|
||||
<EditableInput value={nodeValue} onChange={(nextText) => updatePath(path, nextText)} />
|
||||
)}
|
||||
@@ -263,10 +270,12 @@ const VideoPromptSchemaEditor: React.FC<VideoPromptSchemaEditorProps> = ({ value
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: 14, padding: 12, borderRadius: 10, background: '#f8fafc', color: '#64748b', fontSize: 13, lineHeight: 1.7 }}>
|
||||
{/* <div style={{ marginBottom: 14, padding: 12, borderRadius: 10, background: '#f8fafc', color: '#64748b', fontSize: 13, lineHeight: 1.7 }}>
|
||||
视频时长、比例、清晰度、帧率、推荐分辨率、动态时间规划、输出规格、质量控制、合规控制、schema 协议字段为只读;动作/镜头流程保留原时间段和条目数量,只允许编辑内容描述。
|
||||
</div>
|
||||
{Object.entries(safeValue).map(([key, childValue]) => renderNode(key, childValue, [key]))}
|
||||
</div> */}
|
||||
{Object.entries(safeValue)
|
||||
.filter(([key]) => !HIDDEN_TOP_LEVEL_KEYS.has(key))
|
||||
.map(([key, childValue]) => renderNode(key, childValue, [key]))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -599,6 +599,27 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
|
||||
onChangeComplete={handleRangeChangeComplete}
|
||||
tooltip={{ formatter: (value) => `${toIntegerSecond(Number(value || 0))}s` }}
|
||||
disabled={!integerDuration || disabledByDuration}
|
||||
styles={{
|
||||
track: {
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||
height: 6,
|
||||
borderRadius: 3,
|
||||
},
|
||||
rail: {
|
||||
background: '#e2e8f0',
|
||||
height: 6,
|
||||
borderRadius: 3,
|
||||
},
|
||||
handle: {
|
||||
width: 18,
|
||||
height: 18,
|
||||
marginTop: 0,
|
||||
// backgroundColor: '#fff',
|
||||
// border: '3px solid #6366f1',
|
||||
boxShadow: '0 2px 8px rgba(99, 102, 241, 0.3)',
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -606,7 +627,7 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
|
||||
<div style={{ marginTop: 30, textAlign: 'center', color: '#64748b', fontSize: 16 }}>
|
||||
已选取 {selectedDuration}s
|
||||
<span style={{ marginLeft: 12, fontSize: 13, color: '#94a3b8' }}>
|
||||
最小 {minDuration}s,最大 {maxDuration}s,开始/结束秒数均为整数
|
||||
最小 {minDuration}s,最大 {maxDuration}s
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -830,7 +830,7 @@ const AIChatPage: React.FC = () => {
|
||||
// ==================== 渲染 ====================
|
||||
|
||||
return (
|
||||
<Layout style={{ height: '90vh', background: '#fafafa', overflow: 'auto' }}>
|
||||
<Layout style={{ height: 'calc(100vh - 90px)', background: '#fafafa', overflow: 'auto' }}>
|
||||
{/* 左侧边栏 - 对话列表(已隐藏,保留代码) */}
|
||||
{false && (
|
||||
<Sider
|
||||
|
||||
@@ -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 { getReplicationList, getReplicationDetail, gettwo, getthree, getfour, getEngine, updateHotOpeningVideoPromptSchema } from '../api/index';
|
||||
import { getReplicationList, getReplicationDetail, gettwo, getthree, getfour, getEngine, updateHotOpeningVideoPromptSchema, updateImagePrompt } from '../api/index';
|
||||
import VideoPromptSchemaEditor from '../components/VideoPromptSchemaEditor';
|
||||
import './css/InitialInfo.css';
|
||||
|
||||
@@ -203,8 +203,29 @@ function InitialInfo() {
|
||||
};
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (currentType !== 'video') {
|
||||
setModalVisible(false);
|
||||
if (currentType === 'image') {
|
||||
if (!taskDetail?.id || !editingPromptStepId) {
|
||||
message.warning('缺少任务或步骤 ID,无法保存图片提示词');
|
||||
return;
|
||||
}
|
||||
if (!promptText || !promptText.trim()) {
|
||||
message.warning('图片提示词不能为空');
|
||||
return;
|
||||
}
|
||||
setPromptSaving(true);
|
||||
try {
|
||||
await updateImagePrompt(taskDetail.id, editingPromptStepId, {
|
||||
prompt: promptText.trim(),
|
||||
});
|
||||
message.success('图片提示词已保存');
|
||||
refreshTaskDetail();
|
||||
setModalVisible(false);
|
||||
setEditingPromptStepId('');
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '保存图片提示词失败');
|
||||
} finally {
|
||||
setPromptSaving(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -219,18 +240,11 @@ function InitialInfo() {
|
||||
|
||||
setPromptSaving(true);
|
||||
try {
|
||||
const res: any = await updateHotOpeningVideoPromptSchema(taskDetail.id, editingPromptStepId, {
|
||||
await updateHotOpeningVideoPromptSchema(taskDetail.id, editingPromptStepId, {
|
||||
prompt_schema: formData,
|
||||
});
|
||||
|
||||
if (res?.detail) {
|
||||
setTaskDetail(res.detail);
|
||||
setApiSteps(res.detail.steps || []);
|
||||
} else {
|
||||
refreshTaskDetail();
|
||||
}
|
||||
|
||||
message.success(res?.message || '视频提示词已保存');
|
||||
message.success('视频提示词已保存');
|
||||
refreshTaskDetail();
|
||||
setModalVisible(false);
|
||||
setEditingPromptStepId('');
|
||||
} catch (error: any) {
|
||||
@@ -348,10 +362,12 @@ function InitialInfo() {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div style={{ height: '94vh', background: '#f8fafc' }}>
|
||||
<div style={{ height: 'calc(94vh)', }}>
|
||||
<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' }}>
|
||||
<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"
|
||||
@@ -425,7 +441,8 @@ function InitialInfo() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ width: '28%', minWidth: '400px', background: '#fff', borderRadius: 12, overflowY: 'auto' }}>
|
||||
<div style={{ width: '28%', minWidth: '400px', background: '#fff', borderRadius: 12, overflowY: 'auto', maxHeight: 'calc(100vh - 90px)' }}>
|
||||
|
||||
<div style={{ padding: '14px' }}> 生成步骤</div>
|
||||
<Collapse
|
||||
defaultActiveKey={['']}
|
||||
@@ -531,7 +548,7 @@ function InitialInfo() {
|
||||
<Button
|
||||
type="default"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleOpenModal(step?.output?.payload?.prompt)}
|
||||
onClick={() => handleOpenModal(step?.output?.payload?.prompt, 'image', step.id)}
|
||||
style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
@@ -1053,73 +1070,151 @@ function InitialInfo() {
|
||||
|
||||
{/* 表格 */}
|
||||
<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`);
|
||||
}}
|
||||
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 || '-';
|
||||
}
|
||||
} else if (currentStepCode === 'image_generate') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '等待生成视频提示词';
|
||||
case 'processing':
|
||||
return '融合图生成中';
|
||||
case 'completed':
|
||||
return '融合图生成成功';
|
||||
case 'failed':
|
||||
return '融合图生成失败';
|
||||
default:
|
||||
return status || '-';
|
||||
}
|
||||
} else if (currentStepCode === 'video_prompt_optimize') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '等待最终视频生成';
|
||||
case 'processing':
|
||||
return '视频提示词生成中';
|
||||
case 'completed':
|
||||
return '视频提示词生成成功';
|
||||
case 'failed':
|
||||
return '视频提示词生成失败';
|
||||
default:
|
||||
return status || '-';
|
||||
}
|
||||
} else if (currentStepCode === 'video_generate') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '';
|
||||
case 'processing':
|
||||
return '最终视频生成中';
|
||||
case 'completed':
|
||||
return '最终视频生成成功';
|
||||
case 'failed':
|
||||
return '最终视频生成失败';
|
||||
default:
|
||||
return status || '-';
|
||||
}
|
||||
} else if (currentStepCode === 'material_input') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '等待生成图片提示词';
|
||||
case 'processing':
|
||||
return '素材处理中';
|
||||
case 'completed':
|
||||
return '素材上传成功';
|
||||
case 'failed':
|
||||
return '素材上传失败';
|
||||
default:
|
||||
return status || '-';
|
||||
}
|
||||
} else {
|
||||
const statusMap: Record<string, string> = {
|
||||
'pending': '子任务待处理',
|
||||
'waiting_user': '等待用户确认或触发',
|
||||
'processing': '子任务处理中',
|
||||
'completed': '子任务完成',
|
||||
'failed': '子任务失败',
|
||||
'cancelled': '子任务取消',
|
||||
};
|
||||
return statusMap[status] || status || '-';
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
onClick={() => navigate(`/initial/${record.id}/initialinfo`)}
|
||||
style={{ color: '#6366f1', textDecoration: 'none', fontSize: 12, border: 'none', background: 'none', cursor: 'pointer' }}
|
||||
>
|
||||
>
|
||||
查看详情
|
||||
</button>
|
||||
),
|
||||
},
|
||||
]}
|
||||
dataSource={tableData}
|
||||
rowKey="id"
|
||||
pagination={{
|
||||
current: currentPage,
|
||||
pageSize: pageSize,
|
||||
total: total,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
onChange: handlePageChange,
|
||||
}}
|
||||
style={{ fontSize: 13 }}
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
LoadingOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { uploadVideo, uploadImage, generateReplication, getReplicationList,getone, getReplicationDetail } from '../api';
|
||||
import { uploadVideo, uploadImage, generateReplication, getReplicationList, getone, getReplicationDetail } from '../api';
|
||||
|
||||
const { Header, Content } = Layout;
|
||||
const { TextArea } = Input;
|
||||
@@ -378,10 +378,10 @@ const GenerateConver: React.FC = () => {
|
||||
setOwnProductName('');
|
||||
setProductSellingPoints('');
|
||||
|
||||
fetchCardList(1,8);
|
||||
fetchCardList(1, 8);
|
||||
|
||||
// 获取第一个的id直接进行下一步
|
||||
getReplicationList(1,20).then((res: any) => {
|
||||
getReplicationList(1, 20).then((res: any) => {
|
||||
if (res.items) {
|
||||
setTableData(res.items);
|
||||
// childId = res.items[0].child_id;
|
||||
@@ -406,7 +406,7 @@ const GenerateConver: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="replication-container" style={{ overflow: 'auto', height: '94vh', display: 'flex', justifyContent: 'space-between', gap: '2%' }}>
|
||||
<div className="replication-container" style={{ overflow: 'auto', height: 'calc(100vh - 90px)', display: 'flex', justifyContent: 'space-between', gap: '2%' }}>
|
||||
|
||||
<div className="replication-preview" style={{ width: '70%', height: '100%', background: '#fff', display: 'flex', flexDirection: 'column', borderRadius: 15, overflow: 'hidden' }}>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Button, Drawer, Input, Table, Tag, Upload, message } from 'antd';
|
||||
import { Button, Drawer, Input, 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';
|
||||
@@ -34,6 +34,16 @@ function RemoveInfo() {
|
||||
|
||||
const videoUrl = useMemo(() => buildAssetUrl(taskDetail?.videoUrl), [taskDetail?.videoUrl]);
|
||||
|
||||
const autoSplitButtonText = useMemo(() => {
|
||||
if (tableData.length === 0) {
|
||||
return 'AI自动拆分';
|
||||
}
|
||||
if (tableData.some(item => item.sourceMode === 'ai_suggestion')) {
|
||||
return 'AI重拆';
|
||||
}
|
||||
return 'AI自动拆分';
|
||||
}, [tableData]);
|
||||
|
||||
const fetchTaskDetail = useCallback(async () => {
|
||||
if (!creatID) return;
|
||||
try {
|
||||
@@ -134,11 +144,12 @@ function RemoveInfo() {
|
||||
if (!creatID) return;
|
||||
setAutoSplitLoading(true);
|
||||
try {
|
||||
const shouldReplace = tableData.some(item => item.sourceMode === 'ai_suggestion');
|
||||
await createRemoveLens(creatID, {
|
||||
selected_indices: [],
|
||||
replace_existing: false,
|
||||
replace_existing: shouldReplace,
|
||||
});
|
||||
message.success('AI 拆镜任务已提交');
|
||||
message.success(shouldReplace ? 'AI 重拆任务已提交' : 'AI 拆镜任务已提交');
|
||||
await refreshPageData();
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '拆镜失败');
|
||||
@@ -186,16 +197,19 @@ function RemoveInfo() {
|
||||
return record?.splitStatus === 'completed' && !!record?.segmentVideoUrl;
|
||||
};
|
||||
|
||||
const columns = [
|
||||
const columns: any[] = [
|
||||
{
|
||||
title: '片段',
|
||||
width: 100,
|
||||
fixed: 'left',
|
||||
align: 'center' as const,
|
||||
render: (_: any, record: any) => (
|
||||
<div>
|
||||
<div style={{ fontSize: 16, fontWeight: 600, color: '#333' }}>{record.segmentName || `片段${record.segmentIndex || ''}`}</div>
|
||||
<div style={{ fontSize: 12, color: '#999' }}>{record.timeNode}</div>
|
||||
{record.sourceMode === 'custom' && <Tag color="blue" style={{ marginTop: 6 }}>手动</Tag>}
|
||||
{record.sourceMode === 'ai_suggestion' && <Tag color="orange" style={{ marginTop: 6 }}>自动</Tag>}
|
||||
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@@ -221,13 +235,48 @@ function RemoveInfo() {
|
||||
},
|
||||
{
|
||||
title: '视频内容',
|
||||
width: 250,
|
||||
width: 500,
|
||||
align: 'left' as const,
|
||||
render: (_: any, record: any) => (
|
||||
<div style={{ fontSize: 14, color: '#333', lineHeight: 1.6 }}>
|
||||
{record.segmentContent || record.lastError || '-'}
|
||||
</div>
|
||||
),
|
||||
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') {
|
||||
return <div style={{ fontSize: 14, color: '#333', lineHeight: 1.6 }}>{record.segmentContent || record.lastError || '-'}</div>;
|
||||
}
|
||||
|
||||
const analysisStatus = record.analysis_status || record.analysisStatus;
|
||||
const statusMap: Record<string, string> = {
|
||||
'not_required': '无需单独分析',
|
||||
'pending': '等待分析',
|
||||
'processing': '分析中',
|
||||
'failed': '分析失败',
|
||||
};
|
||||
|
||||
if (analysisStatus === 'processing') {
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<Spin size="small" />
|
||||
<span style={{ fontSize: 14, color: '#f59e0b' }}>分析中</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (analysisStatus === 'failed') {
|
||||
return <div style={{ fontSize: 14, color: '#ef4444', lineHeight: 1.6 }}>分析失败</div>;
|
||||
}
|
||||
|
||||
const displayText = analysisStatus && statusMap[analysisStatus] ? statusMap[analysisStatus] : (record.segmentContent || record.lastError || '-');
|
||||
return <div style={{ fontSize: 14, color: '#333', lineHeight: 1.6 }}>{displayText}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '视频类型',
|
||||
@@ -243,21 +292,114 @@ function RemoveInfo() {
|
||||
title: '状态',
|
||||
width: 120,
|
||||
align: 'center' as const,
|
||||
render: (_: any, record: any) => {
|
||||
const statusMap: Record<string, { text: string; color: string }> = {
|
||||
pending: { text: '待切割', color: 'default' },
|
||||
processing: { text: '切割中', color: 'processing' },
|
||||
retry_waiting: { text: '等待重试', color: 'warning' },
|
||||
completed: { text: '已完成', color: 'success' },
|
||||
failed: { text: '失败', color: 'error' },
|
||||
};
|
||||
const item = statusMap[record.splitStatus] || { text: record.splitStatus || '-', color: 'default' };
|
||||
return <Tag color={item.color}>{item.text}</Tag>;
|
||||
},
|
||||
dataIndex: 'moduleProjectStatus',
|
||||
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 '等待融合图生成';
|
||||
case 'processing':
|
||||
return '图片提示词生成中';
|
||||
case 'completed':
|
||||
return '图片提示词生成成功';
|
||||
case 'failed':
|
||||
return '图片提示词生成失败';
|
||||
default:
|
||||
return status || '-';
|
||||
}
|
||||
} else if (currentStepCode === 'image_generate') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '等待生成视频提示词';
|
||||
case 'processing':
|
||||
return '融合图生成中';
|
||||
case 'completed':
|
||||
return '融合图生成成功';
|
||||
case 'failed':
|
||||
return '融合图生成失败';
|
||||
default:
|
||||
return status || '-';
|
||||
}
|
||||
} else if (currentStepCode === 'video_prompt_optimize') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '等待最终视频生成';
|
||||
case 'processing':
|
||||
return '视频提示词生成中';
|
||||
case 'completed':
|
||||
return '视频提示词生成成功';
|
||||
case 'failed':
|
||||
return '视频提示词生成失败';
|
||||
default:
|
||||
return status || '-';
|
||||
}
|
||||
} else if (currentStepCode === 'video_generate') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '';
|
||||
case 'processing':
|
||||
return '最终视频生成中';
|
||||
case 'completed':
|
||||
return '最终视频生成成功';
|
||||
case 'failed':
|
||||
return '最终视频生成失败';
|
||||
default:
|
||||
return status || '-';
|
||||
}
|
||||
} else if (currentStepCode === 'material_input') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '等待生成图片提示词';
|
||||
case 'processing':
|
||||
return '素材处理中';
|
||||
case 'completed':
|
||||
return '素材上传成功';
|
||||
case 'failed':
|
||||
return '素材上传失败';
|
||||
default:
|
||||
return status || '-';
|
||||
}
|
||||
} else {
|
||||
if (!record.moduleProjectId) {
|
||||
return '待生成任务';
|
||||
}
|
||||
const statusMap: Record<string, string> = {
|
||||
'pending': '子任务待处理',
|
||||
'waiting_user': '等待用户确认或触发',
|
||||
'processing': '子任务处理中',
|
||||
'completed': '子任务完成',
|
||||
'failed': '子任务失败',
|
||||
'cancelled': '子任务取消',
|
||||
};
|
||||
return statusMap[status] || status || '-';
|
||||
}
|
||||
};
|
||||
|
||||
return getStatusText();
|
||||
},
|
||||
|
||||
|
||||
// render: (_: any, record: any) => {
|
||||
// const statusMap: Record<string, { text: string; color: string }> = {
|
||||
// pending: { text: '待切割', color: 'default' },
|
||||
// processing: { text: '切割中', color: 'processing' },
|
||||
// retry_waiting: { text: '等待重试', color: 'warning' },
|
||||
// completed: { text: '已完成', color: 'success' },
|
||||
// failed: { text: '失败', color: 'error' },
|
||||
// };
|
||||
// const item = statusMap[record.splitStatus] || { text: record.splitStatus || '-', color: 'default' };
|
||||
// return <Tag color={item.color}>{item.text}</Tag>;
|
||||
// },
|
||||
},
|
||||
{
|
||||
title: '素材',
|
||||
title: '操作',
|
||||
width: 140,
|
||||
fixed: 'right',
|
||||
align: 'center' as const,
|
||||
render: (_: any, record: any) => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}>
|
||||
@@ -307,8 +449,8 @@ function RemoveInfo() {
|
||||
|
||||
{taskDetail ? (
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', overflow: 'hidden' }}>
|
||||
<div style={{ flex: 0.4, background: '#fff', borderRadius: 12 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 30, marginBottom: 12 }}>
|
||||
<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
|
||||
@@ -331,21 +473,33 @@ function RemoveInfo() {
|
||||
</div>
|
||||
<div style={{ display: 'flex', marginBottom: 12 }}>
|
||||
<span style={{ color: '#999', fontSize: 14, marginRight: 12, width: 80, flexShrink: 0 }}>受众群体:</span>
|
||||
<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>
|
||||
{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>
|
||||
<span style={{ color: '#333' }}>{taskDetail.originalVideoContent}</span>
|
||||
{taskDetail.analysisStatus === 'processing' ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<Spin size="small" tip="分析中" style={{ color: '#f59e0b' }} />
|
||||
</div>
|
||||
) : taskDetail.analysisStatus === 'completed' ? (
|
||||
<span style={{ color: '#333' }}>{taskDetail.originalVideoContent}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* </div> */}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: 16, marginBottom: 20, marginTop: 20 }}>
|
||||
@@ -364,32 +518,71 @@ function RemoveInfo() {
|
||||
>
|
||||
手动
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleAutoGenerate}
|
||||
loading={autoSplitLoading}
|
||||
disabled={autoSplitLoading}
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 48,
|
||||
borderRadius: 8,
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||
fontWeight: 500,
|
||||
border: 'none',
|
||||
}}
|
||||
<Popconfirm
|
||||
title={autoSplitButtonText === 'AI重拆' ? '确定要AI重拆吗?' : '确定要AI拆分吗?'}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
onConfirm={handleAutoGenerate}
|
||||
>
|
||||
自动
|
||||
</Button>
|
||||
<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 镜头拆分方案)
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
|
||||
</div>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 0.6, background: '#fff', borderRadius: 12, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ flex: 2, background: '#fff', borderRadius: 12, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={tableData}
|
||||
pagination={false}
|
||||
bordered={false}
|
||||
rowKey="id"
|
||||
scroll={{ y: '1005' }}
|
||||
scroll={{ y: 450 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -85,7 +85,6 @@ export default function VideoFrameExtractor() {
|
||||
message.success('任务创建成功');
|
||||
// 清空上传内容和输入框
|
||||
cleanupResources();
|
||||
navigate('/');
|
||||
} catch (err) {
|
||||
message.error('任务创建失败,请重试');
|
||||
} finally {
|
||||
@@ -224,31 +223,65 @@ export default function VideoFrameExtractor() {
|
||||
accept="video/*"
|
||||
beforeUpload={handleFileChange}
|
||||
showUploadList={false}
|
||||
disabled={loading}
|
||||
style={{
|
||||
background: '#faf5ff',
|
||||
border: '2px dashed #c4b5fd',
|
||||
background: loading ? '#f3f4f6' : '#faf5ff',
|
||||
border: `2px dashed ${loading ? '#d1d5db' : '#c4b5fd'}`,
|
||||
borderRadius: 24,
|
||||
padding: '60px 20px'
|
||||
padding: '60px 20px',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
}}
|
||||
>
|
||||
<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 }}>
|
||||
支持 MP4、MOV 格式视频,最大支持 100MB 的文件,支持上传 3 分钟内的视频
|
||||
</p>
|
||||
{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={{
|
||||
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: 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 }}>
|
||||
支持 MP4、MOV 格式视频,最大支持 100MB 的文件,支持上传 3 分钟内的视频
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</Upload.Dragger>
|
||||
)}
|
||||
|
||||
@@ -362,7 +395,7 @@ export default function VideoFrameExtractor() {
|
||||
opacity: 0.85,
|
||||
fontWeight: 400
|
||||
}}>
|
||||
预计消耗 10 个积分
|
||||
{/* 预计消耗 10 个积分 */}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -418,6 +451,17 @@ export default function VideoFrameExtractor() {
|
||||
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: '操作',
|
||||
@@ -435,27 +479,17 @@ export default function VideoFrameExtractor() {
|
||||
dataSource={tableData}
|
||||
rowKey="id"
|
||||
pagination={{
|
||||
current: currentPage,
|
||||
pageSize: pageSize,
|
||||
total: total,
|
||||
onChange: handlePageChange,
|
||||
showSizeChanger: false,
|
||||
showQuickJumper: false,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
placement: ['bottomCenter'],
|
||||
itemRender: (_, type, originalElement) => {
|
||||
if (type === 'page') {
|
||||
return <span style={{ borderRadius: 4, margin: '0 4px' }}>{originalElement}</span>;
|
||||
}
|
||||
if (type === 'prev') {
|
||||
return <span style={{ borderRadius: 4, margin: '0 4px' }}>上一页</span>;
|
||||
}
|
||||
if (type === 'next') {
|
||||
return <span style={{ borderRadius: 4, margin: '0 4px' }}>下一页</span>;
|
||||
}
|
||||
return originalElement;
|
||||
},
|
||||
}}
|
||||
current: currentPage,
|
||||
pageSize: pageSize,
|
||||
total: total,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
onChange: handlePageChange,
|
||||
}}
|
||||
style={{ fontSize: 13 }}
|
||||
scroll={{ y: 350 }}
|
||||
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,8 @@ 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 } 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';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
@@ -18,6 +19,8 @@ function InitialInfo() {
|
||||
const [currentType, setCurrentType] = useState<string>('image');
|
||||
const [formData, setFormData] = useState<any>({});
|
||||
const [pollingTimer, setPollingTimer] = useState<any>(null);
|
||||
const [editingPromptStepId, setEditingPromptStepId] = useState('');
|
||||
const [promptSaving, setPromptSaving] = useState(false);
|
||||
|
||||
// 引擎和视频参数相关状态
|
||||
const [enginesele, setEnginesele] = useState<any>({});
|
||||
@@ -183,9 +186,9 @@ function InitialInfo() {
|
||||
}
|
||||
}, [steps]);
|
||||
|
||||
const handleOpenModal = (prompt?: any, type?: string) => {
|
||||
|
||||
const handleOpenModal = (prompt?: any, type?: string, stepId?: string | number) => {
|
||||
setCurrentType(type || 'image');
|
||||
setEditingPromptStepId(stepId ? String(stepId) : '');
|
||||
|
||||
if (type === 'video' && typeof prompt === 'object') {
|
||||
setFormData(prompt);
|
||||
@@ -198,8 +201,56 @@ function InitialInfo() {
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
setModalVisible(false);
|
||||
const handleConfirm = async () => {
|
||||
if (currentType === 'image') {
|
||||
if (!taskDetail?.id || !editingPromptStepId) {
|
||||
message.warning('缺少任务或步骤 ID,无法保存图片提示词');
|
||||
return;
|
||||
}
|
||||
if (!promptText || !promptText.trim()) {
|
||||
message.warning('图片提示词不能为空');
|
||||
return;
|
||||
}
|
||||
setPromptSaving(true);
|
||||
try {
|
||||
await updateShotImagePrompt(taskDetail.id, editingPromptStepId, {
|
||||
prompt: promptText.trim(),
|
||||
});
|
||||
message.success('图片提示词已保存');
|
||||
refreshTaskDetail();
|
||||
setModalVisible(false);
|
||||
setEditingPromptStepId('');
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '保存图片提示词失败');
|
||||
} finally {
|
||||
setPromptSaving(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!taskDetail?.id || !editingPromptStepId) {
|
||||
message.warning('缺少任务或步骤 ID,无法保存视频提示词');
|
||||
return;
|
||||
}
|
||||
if (!formData || typeof formData !== 'object' || Object.keys(formData).length === 0) {
|
||||
message.warning('视频提示词不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
setPromptSaving(true);
|
||||
try {
|
||||
await updateShotVideoPromptSchema(taskDetail.id, editingPromptStepId, {
|
||||
prompt_schema: formData,
|
||||
});
|
||||
message.success('视频提示词已保存');
|
||||
refreshTaskDetail();
|
||||
setModalVisible(false);
|
||||
setEditingPromptStepId('');
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '保存视频提示词失败');
|
||||
} finally {
|
||||
setPromptSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 轮询任务详情
|
||||
@@ -319,9 +370,9 @@ function InitialInfo() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div style={{ minHeight: 'calc(100vh - 90px)', background: '#f8fafc' }}>
|
||||
<div style={{ }}>
|
||||
<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' }}>
|
||||
<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"
|
||||
@@ -395,7 +446,7 @@ function InitialInfo() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ width: '28%', minWidth: '400px', background: '#fff', borderRadius: 12, overflowY: 'auto' }}>
|
||||
<div style={{ width: '28%', minWidth: '400px', background: '#fff', borderRadius: 12, overflowY: 'auto', maxHeight: 'calc(100vh - 90px)' }}>
|
||||
<div style={{ padding: '14px' }}> 生成步骤</div>
|
||||
<Collapse
|
||||
defaultActiveKey={['']}
|
||||
@@ -504,7 +555,7 @@ function InitialInfo() {
|
||||
<Button
|
||||
type="default"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleOpenModal(step?.output?.payload?.prompt)}
|
||||
onClick={() => handleOpenModal(step?.output?.payload?.prompt, 'image', step.id)}
|
||||
style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
@@ -919,7 +970,7 @@ function InitialInfo() {
|
||||
<Button
|
||||
type="default"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleOpenModal(step?.output?.payload?.promptSchema,'video')}
|
||||
onClick={() => handleOpenModal(step?.output?.payload?.promptSchema, 'video', step.id)}
|
||||
style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
@@ -986,7 +1037,7 @@ function InitialInfo() {
|
||||
onCancel={() => setModalVisible(false)}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={() => setModalVisible(false)}>取消</Button>,
|
||||
<Button key="confirm" type="primary" onClick={handleConfirm}>确认</Button>,
|
||||
<Button key="confirm" type="primary" onClick={handleConfirm} loading={promptSaving}>确认</Button>,
|
||||
]}
|
||||
width={800}
|
||||
>
|
||||
@@ -1000,7 +1051,7 @@ function InitialInfo() {
|
||||
/>
|
||||
) : (
|
||||
<div style={{ maxHeight: 500, overflowY: 'auto', paddingRight: 10 }}>
|
||||
<FormRenderer data={formData} onChange={setFormData} />
|
||||
<VideoPromptSchemaEditor value={formData} onChange={setFormData} />
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
Reference in New Issue
Block a user