190 lines
5.0 KiB
JavaScript
190 lines
5.0 KiB
JavaScript
|
|
import request from './request'
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 图生视频API服务
|
|||
|
|
*/
|
|||
|
|
export const imageToVideoApi = {
|
|||
|
|
/**
|
|||
|
|
* 创建图生视频任务
|
|||
|
|
* @param {Object} params - 任务参数
|
|||
|
|
* @param {File} params.firstFrame - 首帧图片
|
|||
|
|
* @param {File} params.lastFrame - 尾帧图片(可选)
|
|||
|
|
* @param {string} params.prompt - 描述文字
|
|||
|
|
* @param {string} params.aspectRatio - 视频比例
|
|||
|
|
* @param {number} params.duration - 视频时长
|
|||
|
|
* @param {boolean} params.hdMode - 是否高清模式
|
|||
|
|
* @returns {Promise} API响应
|
|||
|
|
*/
|
|||
|
|
createTask(params) {
|
|||
|
|
// 参数验证
|
|||
|
|
if (!params) {
|
|||
|
|
throw new Error('参数不能为空')
|
|||
|
|
}
|
|||
|
|
if (!params.firstFrame) {
|
|||
|
|
throw new Error('首帧图片不能为空')
|
|||
|
|
}
|
|||
|
|
if (!params.prompt || params.prompt.trim() === '') {
|
|||
|
|
throw new Error('描述文字不能为空')
|
|||
|
|
}
|
|||
|
|
if (!params.aspectRatio) {
|
|||
|
|
throw new Error('视频比例不能为空')
|
|||
|
|
}
|
|||
|
|
if (!params.duration || params.duration < 1 || params.duration > 60) {
|
|||
|
|
throw new Error('视频时长必须在1-60秒之间')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const formData = new FormData()
|
|||
|
|
|
|||
|
|
// 添加必填参数
|
|||
|
|
formData.append('firstFrame', params.firstFrame)
|
|||
|
|
formData.append('prompt', params.prompt.trim())
|
|||
|
|
formData.append('aspectRatio', params.aspectRatio)
|
|||
|
|
formData.append('duration', params.duration.toString())
|
|||
|
|
formData.append('hdMode', params.hdMode.toString())
|
|||
|
|
|
|||
|
|
// 添加可选参数
|
|||
|
|
if (params.lastFrame) {
|
|||
|
|
formData.append('lastFrame', params.lastFrame)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return request({
|
|||
|
|
url: '/image-to-video/create',
|
|||
|
|
method: 'POST',
|
|||
|
|
data: formData,
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'multipart/form-data'
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取用户任务列表
|
|||
|
|
* @param {number} page - 页码
|
|||
|
|
* @param {number} size - 每页数量
|
|||
|
|
* @returns {Promise} API响应
|
|||
|
|
*/
|
|||
|
|
getTasks(page = 0, size = 10) {
|
|||
|
|
return request({
|
|||
|
|
url: '/image-to-video/tasks',
|
|||
|
|
method: 'GET',
|
|||
|
|
params: { page, size }
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取任务详情
|
|||
|
|
* @param {string} taskId - 任务ID
|
|||
|
|
* @returns {Promise} API响应
|
|||
|
|
*/
|
|||
|
|
getTaskDetail(taskId) {
|
|||
|
|
return request({
|
|||
|
|
url: `/image-to-video/tasks/${taskId}`,
|
|||
|
|
method: 'GET'
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取任务状态
|
|||
|
|
* @param {string} taskId - 任务ID
|
|||
|
|
* @returns {Promise} API响应
|
|||
|
|
*/
|
|||
|
|
getTaskStatus(taskId) {
|
|||
|
|
return request({
|
|||
|
|
url: `/image-to-video/tasks/${taskId}/status`,
|
|||
|
|
method: 'GET'
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 轮询任务状态
|
|||
|
|
* @param {string} taskId - 任务ID
|
|||
|
|
* @param {Function} onProgress - 进度回调
|
|||
|
|
* @param {Function} onComplete - 完成回调
|
|||
|
|
* @param {Function} onError - 错误回调
|
|||
|
|
* @returns {Function} 停止轮询的函数
|
|||
|
|
*/
|
|||
|
|
pollTaskStatus(taskId, onProgress, onComplete, onError) {
|
|||
|
|
let isPolling = true
|
|||
|
|
let pollCount = 0
|
|||
|
|
const maxPolls = 30 // 最大轮询次数(1小时,每2分钟一次)
|
|||
|
|
|
|||
|
|
const poll = async () => {
|
|||
|
|
if (!isPolling || pollCount >= maxPolls) {
|
|||
|
|
if (pollCount >= maxPolls) {
|
|||
|
|
onError && onError(new Error('任务超时'))
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const response = await request({
|
|||
|
|
url: `/image-to-video/tasks/${taskId}/status`,
|
|||
|
|
method: 'GET'
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 检查响应是否有效
|
|||
|
|
if (!response || !response.data || !response.data.success) {
|
|||
|
|
onError && onError(new Error('获取任务状态失败'))
|
|||
|
|
isPolling = false
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const taskData = response.data.data
|
|||
|
|
|
|||
|
|
// 检查taskData是否有效
|
|||
|
|
if (!taskData || !taskData.status) {
|
|||
|
|
onError && onError(new Error('无效的任务数据'))
|
|||
|
|
isPolling = false
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (taskData.status === 'COMPLETED') {
|
|||
|
|
onComplete && onComplete(taskData)
|
|||
|
|
isPolling = false
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (taskData.status === 'FAILED' || taskData.status === 'CANCELLED') {
|
|||
|
|
console.error('任务失败:', {
|
|||
|
|
taskId: taskId,
|
|||
|
|
status: taskData.status,
|
|||
|
|
errorMessage: taskData.errorMessage,
|
|||
|
|
pollCount: pollCount
|
|||
|
|
})
|
|||
|
|
onError && onError(new Error(taskData.errorMessage || '任务失败'))
|
|||
|
|
isPolling = false
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 调用进度回调
|
|||
|
|
onProgress && onProgress({
|
|||
|
|
status: taskData.status,
|
|||
|
|
progress: taskData.progress || 0,
|
|||
|
|
resultUrl: taskData.resultUrl
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
pollCount++
|
|||
|
|
|
|||
|
|
// 继续轮询
|
|||
|
|
setTimeout(poll, 120000) // 每2分钟轮询一次
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('轮询任务状态失败:', error)
|
|||
|
|
onError && onError(error)
|
|||
|
|
isPolling = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 开始轮询
|
|||
|
|
poll()
|
|||
|
|
|
|||
|
|
// 返回停止轮询的函数
|
|||
|
|
return () => {
|
|||
|
|
isPolling = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default imageToVideoApi
|