40 lines
1.9 KiB
TypeScript

import { DETAIL_FONT_FAMILY, type DetailConfig, type DetailDocument } from '@/lib/detail-generator/types'
export class DetailClientError extends Error {
constructor(message: string, readonly status: number) { super(message) }
}
export async function detailRequest<T>(path: string, init: RequestInit = {}): Promise<T> {
const response = await fetch(`/api/detail-generator${path}`, {
...init,
cache: 'no-store',
headers: init.body instanceof FormData ? init.headers : { 'Content-Type': 'application/json', ...init.headers },
})
const data = await response.json().catch(() => ({}))
if (!response.ok) throw new DetailClientError(data.error || '操作未完成,请稍后重试', response.status)
return data as T
}
export function newDetailDocument(defaults?: DetailDocument['models']): DetailDocument {
return {
version: 1,
name: '',
product: { identifierType: 'sku', identifier: '', variant: '', name: '', facts: '', assetIds: [], referenceAssetIds: [], documentAssetIds: [] },
brand: { name: '', tone: '', primaryColor: '#1677ff', backgroundColor: '#ffffff', textColor: '#101828', fontFamily: DETAIL_FONT_FAMILY, status: 'draft' },
models: defaults ? { ...defaults } : { analysis: '', copy: '', image: '' },
analysis: null,
approvedAt: null,
screens: [],
}
}
export function detailModelsAvailable(config: DetailConfig | null, selected: DetailDocument['models'], stages: Array<keyof DetailDocument['models']>): boolean {
return config?.configured === true && stages.every((stage) => !!selected[stage] && (config.models[stage].includes(selected[stage]) || config.legacyModels?.[stage]?.includes(selected[stage])))
}
export function assetUrl(id: string) { return `/api/detail-generator/assets/${encodeURIComponent(id)}` }
export function actionKey(): string {
return globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(36).slice(2)}`
}