🔧 前端修复: 1. 批量处理服务 JSON-RPC 支持: - 为 batchGenerateVideos 添加 JSON-RPC 2.0 格式解析 - 检测 jsonrpc: '2.0' 并提取 result 字段 - 处理 JSON-RPC 错误响应 - 保持向后兼容直接 JSON 格式 2. 详细的调试日志: - 添加批量请求和响应的详细日志 - 显示原始和解析后的结果 - 区分 JSON-RPC 成功和错误响应 - 便于问题排查和调试 3. Store 状态判断增强: - 添加详细的状态检查日志 - 显示 result.status 的值和类型 - 记录成功和失败的处理路径 - 帮助诊断状态识别问题 4. 错误处理统一: - 批量处理和单个处理使用相同的 JSON-RPC 解析逻辑 - 统一的错误信息格式 - 完整的错误详情记录 🎯 问题解决: - 批量处理服务缺少 JSON-RPC 解析 → 添加完整解析逻辑 ✓ - 前端显示失败状态 → 正确提取 JSON-RPC result ✓ - 调试信息不足 → 添加详细日志 ✓ ✅ 修复效果: - 批量处理正确解析 JSON-RPC 响应 - 前端能够识别批量任务的真实状态 - 详细的调试信息便于问题排查 - 统一的 JSON-RPC 处理逻辑 现在批量处理应该能正确显示成功状态!
243 lines
7.4 KiB
TypeScript
243 lines
7.4 KiB
TypeScript
/**
|
||
* AI Video Store
|
||
* Manages AI video generation state and operations
|
||
*/
|
||
|
||
import { create } from 'zustand'
|
||
import { AIVideoService, AIVideoRequest, BatchAIVideoRequest } from '../services/tauri'
|
||
|
||
interface AIVideoJob {
|
||
id: string
|
||
type: 'single' | 'batch'
|
||
status: 'pending' | 'processing' | 'completed' | 'failed'
|
||
progress: number
|
||
request: AIVideoRequest | BatchAIVideoRequest
|
||
result?: any
|
||
error?: string
|
||
startTime: number
|
||
endTime?: number
|
||
}
|
||
|
||
interface AIVideoState {
|
||
// Jobs
|
||
jobs: AIVideoJob[]
|
||
|
||
// Current processing
|
||
isProcessing: boolean
|
||
|
||
// Settings
|
||
defaultPrompts: string[]
|
||
defaultDuration: string
|
||
defaultModelType: string
|
||
|
||
// Actions
|
||
addJob: (request: AIVideoRequest | BatchAIVideoRequest, type: 'single' | 'batch') => string
|
||
updateJob: (jobId: string, updates: Partial<AIVideoJob>) => void
|
||
removeJob: (jobId: string) => void
|
||
clearCompletedJobs: () => void
|
||
|
||
// AI Video operations
|
||
generateSingleVideo: (request: AIVideoRequest) => Promise<string>
|
||
batchGenerateVideos: (request: BatchAIVideoRequest) => Promise<string>
|
||
|
||
// Settings
|
||
setDefaultPrompts: (prompts: string[]) => void
|
||
setDefaultDuration: (duration: string) => void
|
||
setDefaultModelType: (modelType: string) => void
|
||
|
||
// Utility
|
||
setError: (error: string | null) => void
|
||
}
|
||
|
||
// Default prompts from the original GUI
|
||
const DEFAULT_PROMPTS = [
|
||
"女人扭动身体向摄像机展示身材,一只手撩了一下头发,镜头从左向右移动并放大画面",
|
||
"时尚模特抬头自信的展示身材,扭动身体,一只手放在了头上,镜头逐渐放大聚焦在了下衣上",
|
||
"女人扭动身体向摄像机展示身材,一只手撩了一下头发后放在了裤子上,镜头从左向右移动",
|
||
"自信步伐跟拍模特,模特步伐自信地同时行走,镜头紧紧跟随。抬起手捋一捋头发。传递出自信与时尚的气息。",
|
||
"女生两只手捏着拳头轻盈的左右摇摆跳舞,动作幅度不大,然后把手摊开放在胸口再做出像popping心脏跳动的动作,左右身体都要非常协调",
|
||
"一个年轻女子自信地在相机前展示了她优美的身材,以自然的流体动作自由地摇摆,左手撩了一下头发之后停在了胸前。一个美女自拍跳舞扭动身体的视频,手从下到上最后放在胸前,妩媚的表情",
|
||
"美女向后退了一步站在那里展示服装,双手轻轻提了一下裤子两侧,镜头从上到下逐渐放大",
|
||
"女人低头看向裤子,向镜头展示身材,一只手放在了头上做pose动作",
|
||
"美女向后退了一步站在那里展示服装,低头并用手抚摸裤子,镜头从上到下逐渐放大",
|
||
"美女向后退了一步站在那里展示服装,双手从上到下整理衣服,自然扭动身体,自信的表情"
|
||
]
|
||
|
||
export const useAIVideoStore = create<AIVideoState>((set, get) => ({
|
||
// Initial state
|
||
jobs: [],
|
||
isProcessing: false,
|
||
defaultPrompts: DEFAULT_PROMPTS,
|
||
defaultDuration: '5',
|
||
defaultModelType: 'lite',
|
||
|
||
// Job management
|
||
addJob: (request, type) => {
|
||
const job: AIVideoJob = {
|
||
id: crypto.randomUUID(),
|
||
type,
|
||
status: 'pending',
|
||
progress: 0,
|
||
request,
|
||
startTime: Date.now()
|
||
}
|
||
|
||
set(state => ({
|
||
jobs: [...state.jobs, job]
|
||
}))
|
||
|
||
return job.id
|
||
},
|
||
|
||
updateJob: (jobId, updates) => {
|
||
// Throttle rapid updates to prevent excessive re-renders
|
||
const now = Date.now()
|
||
const lastUpdate = get().jobs.find(job => job.id === jobId)?.endTime || 0
|
||
|
||
if (now - lastUpdate < 100 && updates.progress !== undefined) {
|
||
// Skip rapid progress updates
|
||
return
|
||
}
|
||
|
||
set(state => ({
|
||
jobs: state.jobs.map(job =>
|
||
job.id === jobId ? { ...job, ...updates } : job
|
||
)
|
||
}))
|
||
},
|
||
|
||
removeJob: (jobId) => {
|
||
set(state => ({
|
||
jobs: state.jobs.filter(job => job.id !== jobId)
|
||
}))
|
||
},
|
||
|
||
clearCompletedJobs: () => {
|
||
set(state => ({
|
||
jobs: state.jobs.filter(job => job.status !== 'completed' && job.status !== 'failed')
|
||
}))
|
||
},
|
||
|
||
// AI Video operations
|
||
generateSingleVideo: async (request) => {
|
||
const { addJob, updateJob } = get()
|
||
|
||
const jobId = addJob(request, 'single')
|
||
|
||
try {
|
||
set({ isProcessing: true })
|
||
updateJob(jobId, { status: 'processing', progress: 0 })
|
||
|
||
const result = await AIVideoService.generateVideo(request)
|
||
|
||
// Check if the Python script actually succeeded
|
||
if (result && result.status === true) {
|
||
updateJob(jobId, {
|
||
status: 'completed',
|
||
progress: 100,
|
||
result,
|
||
endTime: Date.now()
|
||
})
|
||
} else {
|
||
// Python script returned failure
|
||
const errorMsg = result?.msg || 'Python script execution failed'
|
||
updateJob(jobId, {
|
||
status: 'failed',
|
||
error: errorMsg,
|
||
result,
|
||
endTime: Date.now()
|
||
})
|
||
throw new Error(errorMsg)
|
||
}
|
||
|
||
return jobId
|
||
} catch (error) {
|
||
updateJob(jobId, {
|
||
status: 'failed',
|
||
error: error instanceof Error ? error.message : 'Unknown error',
|
||
endTime: Date.now()
|
||
})
|
||
throw error
|
||
} finally {
|
||
set({ isProcessing: false })
|
||
}
|
||
},
|
||
|
||
batchGenerateVideos: async (request) => {
|
||
const { addJob, updateJob } = get()
|
||
|
||
const jobId = addJob(request, 'batch')
|
||
|
||
try {
|
||
set({ isProcessing: true })
|
||
updateJob(jobId, { status: 'processing', progress: 0 })
|
||
|
||
const result = await AIVideoService.batchGenerateVideos(request)
|
||
console.log('Batch processing result in store:', result)
|
||
console.log('Result status:', result?.status)
|
||
console.log('Result type:', typeof result?.status)
|
||
|
||
// Check if the Python script actually succeeded
|
||
if (result && result.status === true) {
|
||
console.log('Batch processing succeeded, updating job to completed')
|
||
updateJob(jobId, {
|
||
status: 'completed',
|
||
progress: 100,
|
||
result,
|
||
endTime: Date.now()
|
||
})
|
||
} else {
|
||
// Python script returned failure
|
||
console.log('Batch processing failed, result:', result)
|
||
const errorMsg = result?.msg || 'Batch processing failed'
|
||
updateJob(jobId, {
|
||
status: 'failed',
|
||
error: errorMsg,
|
||
result,
|
||
endTime: Date.now()
|
||
})
|
||
throw new Error(errorMsg)
|
||
}
|
||
|
||
return jobId
|
||
} catch (error) {
|
||
updateJob(jobId, {
|
||
status: 'failed',
|
||
error: error instanceof Error ? error.message : 'Unknown error',
|
||
endTime: Date.now()
|
||
})
|
||
throw error
|
||
} finally {
|
||
set({ isProcessing: false })
|
||
}
|
||
},
|
||
|
||
// Settings
|
||
setDefaultPrompts: (prompts) => {
|
||
set({ defaultPrompts: prompts })
|
||
},
|
||
|
||
setDefaultDuration: (duration) => {
|
||
set({ defaultDuration: duration })
|
||
},
|
||
|
||
setDefaultModelType: (modelType) => {
|
||
set({ defaultModelType: modelType })
|
||
},
|
||
|
||
// Utility
|
||
setError: (error) => {
|
||
// This could be used for global error handling
|
||
console.error('AI Video Error:', error)
|
||
}
|
||
}))
|
||
|
||
// Selectors for easier access to specific state
|
||
export const useAIVideoJobs = () => useAIVideoStore(state => state.jobs)
|
||
export const useAIVideoProcessing = () => useAIVideoStore(state => state.isProcessing)
|
||
export const useAIVideoSettings = () => useAIVideoStore(state => ({
|
||
defaultPrompts: state.defaultPrompts,
|
||
defaultDuration: state.defaultDuration,
|
||
defaultModelType: state.defaultModelType
|
||
}))
|