/** * 镭射卡五图批量生成(Phase 1:纯客户端 Canvas,无后端接口) */ import { ref } from 'vue' import { CASTLOVE_FORM_KEY, consumeGenerationFlowPayload, persistLaserPreviewImages, } from '@/utils/castloveGenerationFlow.js' import { generateLaserVariantBatch } from '@/utils/laser-card/laserBatchExport.js' import { segmentPortrait } from '@/composables/useLaserSegment.js' import { useLaserDifyGenerate } from '@/composables/useLaserDifyGenerate.js' const DEFAULT_CANVAS_ID = 'laserBatchCanvas' const DEFAULT_MIN_DURATION_MS = 1600 /** * @param {Object} [options] * @param {string} [options.canvasId] * @param {number} [options.minDurationMs] * @param {number} [options.flowStartedAt] 页面 onMounted 时的时间戳,用于最短展示时长 */ export function useLaserBatchGenerate(options = {}) { const progress = ref(0) const running = ref(false) const error = ref(null) let progressTimer = null let flowStartedAt = options.flowStartedAt ?? Date.now() const canvasId = options.canvasId || DEFAULT_CANVAS_ID const minDurationMs = options.minDurationMs ?? DEFAULT_MIN_DURATION_MS const stopProgress = () => { if (progressTimer) { clearInterval(progressTimer) progressTimer = null } } const simulateProgress = () => { stopProgress() progressTimer = setInterval(() => { if (progress.value < 90) { const increment = Math.random() * 5 + 2 progress.value = Math.min(90, progress.value + increment) } }, 500) } const completeProgress = () => new Promise((resolve) => { stopProgress() const finalInterval = setInterval(() => { if (progress.value < 100) { progress.value = Math.min(100, progress.value + 5) } else { clearInterval(finalInterval) setTimeout(resolve, 500) } }, 50) }) const waitMinDuration = async (minMs) => { if (!minMs || minMs <= 0) return const elapsed = Date.now() - flowStartedAt if (elapsed < minMs) { await new Promise((r) => setTimeout(r, minMs - elapsed)) } } const readFormData = () => { const formStr = uni.getStorageSync(CASTLOVE_FORM_KEY) if (!formStr) { throw new Error('缺少表单数据') } return JSON.parse(formStr) } const resolveImagePath = (formData) => { const imagePath = formData?.image || formData?.uploadedImage || '' if (!imagePath) { throw new Error('缺少上传图片') } return imagePath } /** * 执行五图合成并写入 Storage * @param {Object} [runOptions] * @param {Object} [runOptions.flow] consumeGenerationFlowPayload 结果;缺省则自动 consume * @returns {Promise} 本地临时 JPG 路径列表 */ const run = async (runOptions = {}) => { if (running.value) { throw new Error('生成进行中') } running.value = true error.value = null simulateProgress() try { const flow = runOptions.flow ?? consumeGenerationFlowPayload() const minMs = flow?.minDurationMs ?? minDurationMs const formData = readFormData() const imagePath = resolveImagePath(formData) // Dify 模式:服务端 AI 生成 const genMode ='dify' if (genMode === 'dify') { const dify = useLaserDifyGenerate() const userPrompt = (runOptions.userPrompt || formData?.userPrompt || '').trim() // 先做人像抠图,拿到 PNG 的 OSS 签名地址传给 compositor 叠加 let cutoutUrl = '' try { const cutout = await segmentPortrait(imagePath, { imageBase64: formData.imageBase64 || formData.uploadedImageBase64 || '', }) cutoutUrl = cutout?.cutoutUrl || '' } catch (segErr) { console.warn('[useLaserBatchGenerate] 抠图失败,将不叠加人像:', segErr) } await dify.submit(cutoutUrl, null, userPrompt) const infos = dify.getVariantInfos() const instanceNoVal = dify.getInstanceNo() await waitMinDuration(minMs) persistLaserPreviewImages(infos, instanceNoVal) await completeProgress() return infos } // Client 模式:本地 Canvas 合成 // 人像抠图 → 透明 PNG 叠在底纹之上,再合成五图 const cutout = await segmentPortrait(imagePath, { imageBase64: formData.imageBase64 || formData.uploadedImageBase64 || '', }) // 如果抠图成功,更新 formData 中的 cutoutImage if (cutout?.localPath) { formData.cutoutImage = cutout.localPath } const paths = await generateLaserVariantBatch(cutout.localPath, canvasId) const infos = paths.map((url) => ({ url, oss_key: '' })) await waitMinDuration(minMs) persistLaserPreviewImages(infos) await completeProgress() return infos } catch (e) { error.value = e stopProgress() throw e } finally { running.value = false } } const resetProgress = () => { stopProgress() progress.value = 0 } const setFlowStartedAt = (ts) => { flowStartedAt = ts } return { progress, running, error, simulateProgress, completeProgress, stopProgress, resetProgress, setFlowStartedAt, run, } }