diff --git a/video-gen-app/src/api/index.ts b/video-gen-app/src/api/index.ts index 70122322..0023a96e 100644 --- a/video-gen-app/src/api/index.ts +++ b/video-gen-app/src/api/index.ts @@ -378,6 +378,11 @@ export async function getReplicationDetail(id: string): Promise { export async function getone(projectId: string, stepId: string): Promise { return api.post(`/hot-opening-replications/tasks/${projectId}/steps/${stepId}/generate-image-prompt`); } + +// 修改图片生成提示词 +export async function updateImagePrompt(projectId: string, stepId: string, params: any): Promise { + return api.put(`/hot-opening-replications/tasks/${projectId}/steps/${stepId}/image-prompt`, params); +} // 第二步,生成图片 export async function gettwo(projectId: string, stepId: string ,params: any): Promise { @@ -534,6 +539,16 @@ export async function removeone(projectId: string, stepId: string): Promise } +// 修改图片提示词 +export async function updateShotImagePrompt(projectId: string, stepId: string, params: any): Promise { + return api.put(`/shot-replications/projects/${projectId}/steps/${stepId}/image-prompt`, params); +} + +// 修改视频提示词 +export async function updateShotVideoPromptSchema(projectId: string, stepId: string, params: any): Promise { + return api.put(`/shot-replications/projects/${projectId}/steps/${stepId}/video-prompt-schema`, params); +} + // 第二步,生成图片 export async function removetwo(projectId: string, stepId: string ,params: any): Promise { return api.post(`/shot-replications/projects/${projectId}/steps/${stepId}/generate-image`, params); diff --git a/video-gen-app/src/components/VideoPromptSchemaEditor.tsx b/video-gen-app/src/components/VideoPromptSchemaEditor.tsx index eb865c92..b3188cbb 100644 --- a/video-gen-app/src/components/VideoPromptSchemaEditor.tsx +++ b/video-gen-app/src/components/VideoPromptSchemaEditor.tsx @@ -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 = ({ value
{fieldTitle(key)} - {!editableArray && 锁定结构} + {/* {!editableArray && 锁定结构} */} {editableArray && (
- {(rootKey === '动作流程' || rootKey === '镜头流程') && ( + {/* {(rootKey === '动作流程' || rootKey === '镜头流程') && ( 时间段和条目数量由后端锁定,只允许编辑每段里的动作、镜头、说明等内容。 - )} + )} */} ); } @@ -226,7 +232,7 @@ const VideoPromptSchemaEditor: React.FC = ({ value
{fieldTitle(key)} - {lockedSection && 只读} + {/* {lockedSection && 只读} */}
= ({ value
{fieldTitle(key)} - {locked && 只读} + {/* {locked && 只读12312} */} {locked ? ( + // <> ) : ( updatePath(path, nextText)} /> )} @@ -263,10 +270,12 @@ const VideoPromptSchemaEditor: React.FC = ({ value return (
-
+ {/*
视频时长、比例、清晰度、帧率、推荐分辨率、动态时间规划、输出规格、质量控制、合规控制、schema 协议字段为只读;动作/镜头流程保留原时间段和条目数量,只允许编辑内容描述。 -
- {Object.entries(safeValue).map(([key, childValue]) => renderNode(key, childValue, [key]))} +
*/} + {Object.entries(safeValue) + .filter(([key]) => !HIDDEN_TOP_LEVEL_KEYS.has(key)) + .map(([key, childValue]) => renderNode(key, childValue, [key]))}
); }; diff --git a/video-gen-app/src/components/VideoTrimPicker.tsx b/video-gen-app/src/components/VideoTrimPicker.tsx index 1322379a..81d4096b 100644 --- a/video-gen-app/src/components/VideoTrimPicker.tsx +++ b/video-gen-app/src/components/VideoTrimPicker.tsx @@ -599,6 +599,27 @@ const VideoTrimPicker: React.FC = ({ 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)', + }, + }} />
@@ -606,7 +627,7 @@ const VideoTrimPicker: React.FC = ({
已选取 {selectedDuration}s - 最小 {minDuration}s,最大 {maxDuration}s,开始/结束秒数均为整数 + 最小 {minDuration}s,最大 {maxDuration}s
diff --git a/video-gen-app/src/pages/GenerateConver.tsx b/video-gen-app/src/pages/GenerateConver.tsx index a75e20d8..1369bec2 100644 --- a/video-gen-app/src/pages/GenerateConver.tsx +++ b/video-gen-app/src/pages/GenerateConver.tsx @@ -830,7 +830,7 @@ const AIChatPage: React.FC = () => { // ==================== 渲染 ==================== return ( - + {/* 左侧边栏 - 对话列表(已隐藏,保留代码) */} {false && ( { - 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 ( -
-
+
+
+
-
+
+
-
+
+
生成步骤
} - 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() { {/* 表格 */} { - const statusMap: Record = { - '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) => ( - - ), - }, - ]} - dataSource={tableData} - rowKey="id" - pagination={{ - current: currentPage, - pageSize: pageSize, - total: total, - showSizeChanger: true, - showQuickJumper: true, - showTotal: (total) => `共 ${total} 条`, - onChange: handlePageChange, - }} - style={{ fontSize: 13 }} - /> + + ), + }, + ]} + 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 }} + /> ); diff --git a/video-gen-app/src/pages/InitialReplication.tsx b/video-gen-app/src/pages/InitialReplication.tsx index b7e1be31..45716da9 100644 --- a/video-gen-app/src/pages/InitialReplication.tsx +++ b/video-gen-app/src/pages/InitialReplication.tsx @@ -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; @@ -59,7 +59,7 @@ const GenerateConver: React.FC = () => { // 卡片列表轮询定时器(使用 ref 避免闭包问题) const cardPollingTimer = useRef(null); - + // 表格轮询定时器(使用 ref 避免闭包问题) const tablePollingTimer = useRef(null); @@ -82,11 +82,11 @@ const GenerateConver: React.FC = () => { // 首次加载或分页变化时:直接替换 setCardData(res.items); } - + // 检查是否有 processing 状态 const hasProcessing = res.items.some(item => item.status === 'processing'); - - + + // 如果有 processing 状态且轮询未启动,启动轮询 if (hasProcessing && !cardPollingTimer.current) { cardPollingTimer.current = setInterval(() => { @@ -132,10 +132,10 @@ const GenerateConver: React.FC = () => { // 首次加载或分页变化时:直接替换 setTableData(res.items); } - + // 检查是否有 processing 状态 const hasProcessing = res.items.some(item => item.status === 'processing'); - + // 如果有 processing 状态且轮询未启动,启动轮询 if (hasProcessing && !tablePollingTimer.current) { tablePollingTimer.current = setInterval(() => { @@ -377,11 +377,11 @@ const GenerateConver: React.FC = () => { setOriginalProductName(''); 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; @@ -395,7 +395,7 @@ const GenerateConver: React.FC = () => { } }).catch((error: any) => { }); - + @@ -406,7 +406,7 @@ const GenerateConver: React.FC = () => { }; return ( -
+
@@ -469,8 +469,8 @@ const GenerateConver: React.FC = () => {
{item.finalVideoUrl ? ( - - + + {item.targetProjectName} { {(() => { const status = item.status; const currentStepCode = item.current_step_code || item.currentStepCode; - + if (currentStepCode === 'image_prompt_optimize') { switch (status) { case 'waiting_user': @@ -1031,7 +1031,7 @@ const GenerateConver: React.FC = () => { 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) { @@ -1110,7 +1110,7 @@ const GenerateConver: React.FC = () => { return statusMap[status] || status || '-'; } }; - + return getStatusText(); }, }, diff --git a/video-gen-app/src/pages/RemoveInfo.tsx b/video-gen-app/src/pages/RemoveInfo.tsx index e912f669..61463db4 100644 --- a/video-gen-app/src/pages/RemoveInfo.tsx +++ b/video-gen-app/src/pages/RemoveInfo.tsx @@ -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) => (
{record.segmentName || `片段${record.segmentIndex || ''}`}
{record.timeNode}
{record.sourceMode === 'custom' && 手动} + {record.sourceMode === 'ai_suggestion' && 自动} +
), }, @@ -221,13 +235,48 @@ function RemoveInfo() { }, { title: '视频内容', - width: 250, + width: 500, align: 'left' as const, - render: (_: any, record: any) => ( -
- {record.segmentContent || record.lastError || '-'} -
- ), + 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
{record.segmentContent || record.lastError || '-'}
; + } + + const analysisStatus = record.analysis_status || record.analysisStatus; + const statusMap: Record = { + 'not_required': '无需单独分析', + 'pending': '等待分析', + 'processing': '分析中', + 'failed': '分析失败', + }; + + if (analysisStatus === 'processing') { + return ( +
+ + 分析中 +
+ ); + } + + if (analysisStatus === 'failed') { + return
分析失败
; + } + + const displayText = analysisStatus && statusMap[analysisStatus] ? statusMap[analysisStatus] : (record.segmentContent || record.lastError || '-'); + return
{displayText}
; + }, }, { title: '视频类型', @@ -243,21 +292,114 @@ function RemoveInfo() { title: '状态', width: 120, align: 'center' as const, - render: (_: any, record: any) => { - const statusMap: Record = { - 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 {item.text}; - }, + 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 = { + 'pending': '子任务待处理', + 'waiting_user': '等待用户确认或触发', + 'processing': '子任务处理中', + 'completed': '子任务完成', + 'failed': '子任务失败', + 'cancelled': '子任务取消', + }; + return statusMap[status] || status || '-'; + } + }; + + return getStatusText(); + }, + + + // render: (_: any, record: any) => { + // const statusMap: Record = { + // 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 {item.text}; + // }, }, { - title: '素材', + title: '操作', width: 140, + fixed: 'right', align: 'center' as const, render: (_: any, record: any) => (
@@ -307,8 +449,8 @@ function RemoveInfo() { {taskDetail ? (
-
-
+
+ {/*
*/}
-
+ {/*
*/}
@@ -364,32 +518,71 @@ function RemoveInfo() { > 手动 - +
+
+ } + placement="top" + trigger="hover" + overlayInnerStyle={{ maxWidth: 400, background: '#fff', border: '1px solid #e5e7eb', borderRadius: 12, padding: 12 }} + > +
+ (AI 镜头拆分方案) +
+ + )} + + + +
+
-
+
diff --git a/video-gen-app/src/pages/RemoveLens.tsx b/video-gen-app/src/pages/RemoveLens.tsx index 8a010d02..19aa4f8a 100644 --- a/video-gen-app/src/pages/RemoveLens.tsx +++ b/video-gen-app/src/pages/RemoveLens.tsx @@ -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', }} > -
- -
-

- 点击或拖拽到此上传视频文件 -

-

- 支持 MP4、MOV 格式视频,最大支持 100MB 的文件,支持上传 3 分钟内的视频 -

+ {loading ? ( +
+
+
+
+

+ 上传中... +

+

+ 请稍候,视频正在上传 +

+
+ ) : ( + <> +
+ +
+

+ 点击或拖拽到此上传视频文件 +

+

+ 支持 MP4、MOV 格式视频,最大支持 100MB 的文件,支持上传 3 分钟内的视频 +

+ + )} )} @@ -362,7 +395,7 @@ export default function VideoFrameExtractor() { opacity: 0.85, fontWeight: 400 }}> - 预计消耗 10 个积分 + {/* 预计消耗 10 个积分 */}
@@ -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 {originalElement}; - } - if (type === 'prev') { - return 上一页; - } - if (type === 'next') { - return 下一页; - } - return originalElement; - }, - }} + current: currentPage, + pageSize: pageSize, + total: total, + showSizeChanger: true, + showQuickJumper: true, + showTotal: (total) => `共 ${total} 条`, + onChange: handlePageChange, + }} + style={{ fontSize: 13 }} + scroll={{ y: 350 }} + /> diff --git a/video-gen-app/src/pages/RemoveRw.tsx b/video-gen-app/src/pages/RemoveRw.tsx index 3504f1de..3e214d10 100644 --- a/video-gen-app/src/pages/RemoveRw.tsx +++ b/video-gen-app/src/pages/RemoveRw.tsx @@ -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('image'); const [formData, setFormData] = useState({}); const [pollingTimer, setPollingTimer] = useState(null); + const [editingPromptStepId, setEditingPromptStepId] = useState(''); + const [promptSaving, setPromptSaving] = useState(false); // 引擎和视频参数相关状态 const [enginesele, setEnginesele] = useState({}); @@ -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 (
-
+
-
+
-
+
生成步骤
} - 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() { , - , + , ]} width={800} > @@ -1000,7 +1051,7 @@ function InitialInfo() { /> ) : (
- +
)}