219 lines
4.9 KiB
TypeScript
Executable File
219 lines
4.9 KiB
TypeScript
Executable File
import type { Asset, PipelineProgress, PipelineStage, Project, Task } from './types'
|
|
|
|
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
|
const randomDelay = () => delay(300 + Math.random() * 500)
|
|
|
|
// Mock 数据
|
|
const MOCK_PROJECT: Project = {
|
|
id: 'proj-default',
|
|
name: '我的工程',
|
|
style: {
|
|
kvPairs: {
|
|
artStyle: 'pixel',
|
|
palette: 'warm',
|
|
lineWeight: 'thin',
|
|
scene: 'forest',
|
|
lighting: 'bright',
|
|
mood: 'cheerful',
|
|
},
|
|
},
|
|
createdAt: '2026-05-20T10:00:00Z',
|
|
}
|
|
|
|
const MOCK_TASKS: Task[] = [
|
|
{
|
|
id: 'task-001',
|
|
projectId: 'proj-default',
|
|
prompt: '一个拿剑的小人',
|
|
assetType: 'sprite',
|
|
status: 'completed',
|
|
progress: 100,
|
|
createdAt: '2026-05-24T09:00:00Z',
|
|
updatedAt: '2026-05-24T09:02:00Z',
|
|
},
|
|
{
|
|
id: 'task-002',
|
|
projectId: 'proj-default',
|
|
prompt: '森林背景',
|
|
assetType: 'background',
|
|
status: 'failed',
|
|
error: '生成超时',
|
|
createdAt: '2026-05-24T08:00:00Z',
|
|
updatedAt: '2026-05-24T08:10:00Z',
|
|
},
|
|
{
|
|
id: 'task-003',
|
|
projectId: 'proj-default',
|
|
prompt: '魔法药水瓶',
|
|
assetType: 'ui',
|
|
status: 'completed',
|
|
progress: 100,
|
|
createdAt: '2026-05-23T15:00:00Z',
|
|
updatedAt: '2026-05-23T15:03:00Z',
|
|
},
|
|
]
|
|
|
|
const MOCK_ASSETS: Asset[] = [
|
|
{
|
|
id: 'asset-001',
|
|
key: 'generation/proj-001/task-001/0.png',
|
|
url: 'https://placehold.co/256x256/e94560/1a1a2e?text=Sprite',
|
|
format: 'png',
|
|
width: 256,
|
|
height: 256,
|
|
metadata: {
|
|
frameWidth: 64,
|
|
frameHeight: 64,
|
|
frameCount: 16,
|
|
directions: 4,
|
|
},
|
|
},
|
|
]
|
|
|
|
// Mock API 函数
|
|
export async function mockGetProject(_id: string): Promise<Project> {
|
|
await randomDelay()
|
|
return { ...MOCK_PROJECT }
|
|
}
|
|
|
|
export async function mockGetStyle(
|
|
_projectId: string
|
|
): Promise<Record<string, string>> {
|
|
await randomDelay()
|
|
return { ...MOCK_PROJECT.style.kvPairs }
|
|
}
|
|
|
|
export async function mockSaveStyle(
|
|
_projectId: string,
|
|
_kvPairs: Record<string, string>
|
|
): Promise<void> {
|
|
await randomDelay()
|
|
}
|
|
|
|
export async function mockGetTasks(_projectId: string): Promise<Task[]> {
|
|
await randomDelay()
|
|
return MOCK_TASKS.map(t => ({ ...t }))
|
|
}
|
|
|
|
export async function mockGetTask(taskId: string): Promise<Task> {
|
|
await randomDelay()
|
|
const task = MOCK_TASKS.find(t => t.id === taskId)
|
|
if (!task) throw new Error('任务不存在')
|
|
return { ...task }
|
|
}
|
|
|
|
export async function mockGetAssets(_taskId: string): Promise<Asset[]> {
|
|
await randomDelay()
|
|
return MOCK_ASSETS.map(a => ({ ...a }))
|
|
}
|
|
|
|
export async function mockSubmitGenerate(
|
|
_projectId: string,
|
|
prompt: string,
|
|
assetType: string
|
|
): Promise<string> {
|
|
await delay(500)
|
|
const taskId = `task-${Date.now()}`
|
|
MOCK_TASKS.unshift({
|
|
id: taskId,
|
|
projectId: _projectId,
|
|
prompt,
|
|
assetType,
|
|
status: 'pending',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
})
|
|
return taskId
|
|
}
|
|
|
|
// 模拟 WebSocket 管线进度
|
|
const STAGES: PipelineStage[] = [
|
|
'prompt_builder',
|
|
'asset_generator',
|
|
'quality_supervisor',
|
|
'format_adapter',
|
|
]
|
|
|
|
const STAGE_LABELS: Record<PipelineStage, string> = {
|
|
prompt_builder: '提示词构建',
|
|
asset_generator: '素材生成',
|
|
quality_supervisor: '质量检查',
|
|
format_adapter: '格式转换',
|
|
}
|
|
|
|
export { STAGE_LABELS }
|
|
|
|
export function createMockWebSocket(
|
|
_taskId: string,
|
|
onProgress: (msg: PipelineProgress) => void,
|
|
onComplete: (assets: Asset[]) => void,
|
|
_onError?: (error: string) => void
|
|
): () => void {
|
|
let stageIndex = 0
|
|
let tick = 0
|
|
let retryCount = 0
|
|
const maxRetries = 1
|
|
let cancelled = false
|
|
|
|
const interval = setInterval(() => {
|
|
if (cancelled) return
|
|
|
|
const stage = STAGES[stageIndex]
|
|
const stageProgress = (tick % 3) * 50 // 0, 50, 100
|
|
|
|
// 质检阶段有概率重试
|
|
if (
|
|
stage === 'quality_supervisor' &&
|
|
stageProgress === 100 &&
|
|
retryCount < maxRetries &&
|
|
Math.random() < 0.3
|
|
) {
|
|
onProgress({
|
|
stage: 'quality_supervisor',
|
|
status: 'failed',
|
|
progress: 100,
|
|
retryCount: retryCount + 1,
|
|
rejectReason: '风格一致性不足',
|
|
})
|
|
retryCount++
|
|
stageIndex = 0
|
|
tick = 0
|
|
return
|
|
}
|
|
|
|
const isLastTickOfStage = stageProgress === 100
|
|
const isLastStage = stageIndex === STAGES.length - 1
|
|
|
|
if (isLastTickOfStage && isLastStage) {
|
|
// 管线完成
|
|
onProgress({
|
|
stage: 'format_adapter',
|
|
status: 'completed',
|
|
progress: 100,
|
|
result: { assets: MOCK_ASSETS },
|
|
})
|
|
onComplete(MOCK_ASSETS)
|
|
clearInterval(interval)
|
|
return
|
|
}
|
|
|
|
onProgress({
|
|
stage,
|
|
status: isLastTickOfStage ? 'completed' : 'running',
|
|
progress: stageProgress,
|
|
retryCount: retryCount > 0 ? retryCount : undefined,
|
|
})
|
|
|
|
tick++
|
|
if (isLastTickOfStage) {
|
|
stageIndex++
|
|
tick = 0
|
|
}
|
|
}, 1000)
|
|
|
|
return () => {
|
|
cancelled = true
|
|
clearInterval(interval)
|
|
}
|
|
}
|