/** * 铸爱 — 统一「Thinking → 选择 → 详情确认 → 铸造」路由与 Storage */ import { LENTICULAR_STUDIO_STORAGE_KEY, CASTLOVE_LASER_ENTRY_KEY, } from '@/utils/castloveMintForm.js' export const GENERATION_FLOW_KEY = 'generation_flow_payload' export const GENERATION_REQUEST_KEY = 'generation_request_data' export const GENERATED_IMAGES_KEY = 'generated_images' export const GENERATION_RESULT_META_KEY = 'generation_result_meta' export const CASTLOVE_FORM_KEY = 'castlove_form_data' export const CRAFT_SELECTED_IMAGE_KEY = 'craft_selected_image' export const CRAFT_SELECTED_INDEX_KEY = 'craft_selected_index' export const FLOW_MODE_API = 'api' export const FLOW_MODE_PREFILLED = 'prefilled' export const FLOW_MODE_LENTICULAR = 'lenticular' export const FLOW_MODE_LASER = 'laser' export const AFTER_SELECT_MINT = 'mint' export const AFTER_SELECT_DETAIL = 'detail' export const STUDIO_LENTICULAR = 'lenticular' export const STUDIO_LASER = 'laser' const LOADING_URL = '/pages/discover/generation-loading' const RESULT_URL = '/pages/discover/generation-result' const ASSET_DETAIL_URL = '/pages/asset-detail/asset-detail' export function padImagesForSelection(images, minCount = 4) { if (!Array.isArray(images) || images.length === 0) { return [] } if (images.length >= minCount) { return images.slice(0, minCount) } const out = [...images] while (out.length < minCount) { out.push(images[out.length % images.length]) } return out } function persistFormData(formData) { if (formData != null) { uni.setStorageSync(CASTLOVE_FORM_KEY, JSON.stringify(formData)) } } function enrichFormData(formData, { afterSelect, studioKind } = {}) { const next = { ...(formData || {}) } if (afterSelect) { next.generation_after = afterSelect } if (studioKind) { next.studio_kind = studioKind } return next } export function materializeImageRef(src) { const s = String(src || '').trim() if (!s) { return Promise.resolve({ path: '', base64: '' }) } if (s.startsWith('data:')) { return Promise.resolve({ path: '', base64: s }) } if (s.startsWith('http://') || s.startsWith('https://')) { return new Promise((resolve, reject) => { uni.downloadFile({ url: s, success: (res) => { if (res.statusCode === 200 && res.tempFilePath) { resolve({ path: res.tempFilePath, base64: '' }) } else { reject(new Error('图片下载失败')) } }, fail: (err) => reject(err || new Error('图片下载失败')), }) }) } return Promise.resolve({ path: s, base64: '' }) } function navigateToLoading() { uni.navigateTo({ url: LOADING_URL }) } export function startAiImageGenerationFlow({ prompt, formData, n = 4, model = 'image-01', aspectRatio = '16:9', afterSelect = AFTER_SELECT_MINT, studioKind = '', }) { const requestData = { prompt, model, aspect_ratio: aspectRatio, subject_reference: [{ type: 'character', image_file: '' }], n, } const merged = enrichFormData(formData, { afterSelect, studioKind }) persistFormData(merged) uni.setStorageSync(GENERATION_REQUEST_KEY, JSON.stringify(requestData)) uni.setStorageSync( GENERATION_FLOW_KEY, JSON.stringify({ mode: FLOW_MODE_API, craft: merged?.craft_name || merged?.typeName || '', afterSelect, studioKind, }) ) navigateToLoading() } /** 光栅 / 镭射:工作台生成预览,不走通用 AI 四图 */ export function startCraftGenerationFlow({ formData, studioKind }) { const merged = enrichFormData(formData, { afterSelect: AFTER_SELECT_DETAIL, studioKind, }) persistFormData(merged) uni.removeStorageSync(GENERATION_REQUEST_KEY) const mode = studioKind === STUDIO_LENTICULAR ? FLOW_MODE_LENTICULAR : FLOW_MODE_LASER uni.setStorageSync( GENERATION_FLOW_KEY, JSON.stringify({ mode, studioKind, afterSelect: AFTER_SELECT_DETAIL, craft: merged?.craft_name || merged?.typeName || '', minDurationMs: 1600, }) ) navigateToLoading() } export function startPrefilledSelectionFlow({ images, formData, minDurationMs = 1400, padToFour = true, afterSelect = AFTER_SELECT_MINT, studioKind = '', }) { const list = padToFour ? padImagesForSelection(images) : images if (!list.length) { throw new Error('startPrefilledSelectionFlow: images 不能为空') } const merged = enrichFormData(formData, { afterSelect, studioKind }) persistFormData(merged) uni.removeStorageSync(GENERATION_REQUEST_KEY) uni.setStorageSync( GENERATION_FLOW_KEY, JSON.stringify({ mode: FLOW_MODE_PREFILLED, images: list, minDurationMs, craft: merged?.craft_name || merged?.typeName || '', afterSelect, studioKind, }) ) navigateToLoading() } export function persistLenticularPreviewMeta(formData) { uni.setStorageSync( LENTICULAR_STUDIO_STORAGE_KEY, JSON.stringify({ bgPath: formData.lenticularBgImage || '', subjectPath: formData.lenticularSubjectImage || '', bgBase64: formData.lenticularBgBase64 || '', subjectBase64: formData.lenticularSubjectBase64 || '', nftInfo: formData.info || formData.nftInfo || '', materialTypeIndex: formData.materialTypeIndex ?? 0, aiDescription: formData.aiDescription || '', }) ) uni.setStorageSync( GENERATION_RESULT_META_KEY, JSON.stringify({ displayMode: STUDIO_LENTICULAR, imageCount: 1 }) ) uni.setStorageSync(GENERATED_IMAGES_KEY, JSON.stringify([{ type: 'lenticular' }])) } export function persistLaserPreviewImages(paths) { uni.setStorageSync( GENERATION_RESULT_META_KEY, JSON.stringify({ displayMode: STUDIO_LASER, imageCount: paths.length }) ) uni.setStorageSync(GENERATED_IMAGES_KEY, JSON.stringify(paths)) } export async function completeSelectionAndOpenDetail({ selectedImage, selectedIndex, formData, }) { const img = await materializeImageRef(selectedImage) const storedImage = img.path || img.base64 || selectedImage uni.setStorageSync(CRAFT_SELECTED_IMAGE_KEY, storedImage) uni.setStorageSync(CRAFT_SELECTED_INDEX_KEY, String(selectedIndex ?? 0)) if (formData?.studio_kind === STUDIO_LENTICULAR) { const subjectRef = storedImage uni.setStorageSync( LENTICULAR_STUDIO_STORAGE_KEY, JSON.stringify({ bgPath: formData.lenticularBgImage || '', subjectPath: subjectRef, bgBase64: formData.lenticularBgBase64 || '', subjectBase64: img.base64 || formData.lenticularSubjectBase64 || '', nftInfo: formData.info || formData.nftInfo || '', materialTypeIndex: formData.materialTypeIndex ?? 0, aiDescription: formData.aiDescription || '', }) ) } else if (formData?.studio_kind === STUDIO_LASER) { uni.setStorageSync( CASTLOVE_LASER_ENTRY_KEY, JSON.stringify({ nftInfo: formData.info || formData.nftInfo || '', materialTypes: formData.materialTypes || [], materialTypeIndex: formData.materialTypeIndex ?? 0, pageName: formData.craft_name || formData.typeName || '', uploadedImage: storedImage, uploadedImageBase64: img.base64 || formData.uploadedImageBase64 || '', }) ) } const kind = formData?.studio_kind || '' uni.navigateTo({ url: `${ASSET_DETAIL_URL}?from=craft_confirm&studio_kind=${encodeURIComponent(kind)}`, }) } export function isDetailAfterSelect(formData) { return formData?.generation_after === AFTER_SELECT_DETAIL } export function isLenticularKind(formData) { return formData?.studio_kind === STUDIO_LENTICULAR } export function isLaserKind(formData) { return formData?.studio_kind === STUDIO_LASER } export function consumeGenerationFlowPayload() { try { const raw = uni.getStorageSync(GENERATION_FLOW_KEY) uni.removeStorageSync(GENERATION_FLOW_KEY) if (!raw) { return null } return JSON.parse(raw) } catch (e) { console.error('[castloveGenerationFlow] consume payload', e) return null } } export { RESULT_URL }