/** * AI 生成失败的普通用户展示层。 * * 页面只消费这里的稳定 code,不解析供应商名称、HTTP 状态、URL 或英文异常。 * 本文件不发请求、不依赖 React,后续脚本/图片/视频页面可按需接入。 */ export type GenerationOperation = | "script_generate" | "image_generate" | "base_asset_generate" | "triview_generate" | "storyboard_generate" | "video_generate" | "voiceover_generate" | "entity_extract"; export type GenerationErrorCode = | "provider_quota_exhausted" | "provider_rate_limited" | "provider_unavailable" | "provider_config_error" | "model_unavailable" | "content_rejected" | "invalid_input" | "asset_unavailable" | "user_credit_insufficient" | "task_timeout" | "processing_failed" | "unknown"; export type GenerationErrorAction = "retry" | "retry_later" | "revise_input" | "recharge" | "contact_support" | "none"; export type PublicGenerationError = { domain: "generation"; code: GenerationErrorCode; operation: GenerationOperation; action: GenerationErrorAction; retryable: boolean; reference_id?: string; }; export type GenerationErrorPresentation = { title: string; description: string; action: GenerationErrorAction; retryable: boolean; referenceId?: string; }; const OPERATION_LABEL: Record = { script_generate: "脚本", image_generate: "图片", base_asset_generate: "基础素材", triview_generate: "三视图", storyboard_generate: "故事板", video_generate: "视频", voiceover_generate: "配音", entity_extract: "脚本信息" }; type Copy = (operation: string) => Pick; const ERROR_COPY: Record = { provider_quota_exhausted: (operation) => ({ title: `${operation}暂时不可用`, description: "生成服务正在处理中,请稍后再试。" }), provider_rate_limited: (operation) => ({ title: `${operation}请求较多`, description: "当前生成请求较多,请稍后再试。" }), provider_unavailable: (operation) => ({ title: `${operation}暂时不可用`, description: "生成服务暂时不可用,请稍后重试。" }), provider_config_error: (operation) => ({ title: `${operation}暂时不可用`, description: "平台正在处理该问题,请稍后再试。" }), model_unavailable: (operation) => ({ title: `${operation}暂时不可用`, description: "当前生成能力暂不可用,请稍后再试。" }), content_rejected: (operation) => ({ title: `${operation}内容需要调整`, description: "内容未通过生成审核,请调整描述或素材后重试。" }), invalid_input: (operation) => ({ title: `${operation}内容需检查`, description: "请检查描述、参数或素材格式后重试。" }), asset_unavailable: () => ({ title: "引用素材当前不可用", description: "请重新上传或更换素材后重试。" }), user_credit_insufficient: () => ({ title: "可用积分不足", description: "充值后可继续生成。" }), task_timeout: (operation) => ({ title: `${operation}生成时间过长`, description: "服务响应较慢,未完成生成,请重试。" }), processing_failed: (operation) => ({ title: `${operation}结果处理失败`, description: "本次未生成可用结果,请重试。" }), unknown: (operation) => ({ title: `${operation}遇到问题`, description: "请稍后重试;若多次出现,请提供任务编号以便排查。" }) }; export function isPublicGenerationError(value: unknown): value is PublicGenerationError { if (!value || typeof value !== "object") return false; const candidate = value as Partial; return candidate.domain === "generation" && typeof candidate.code === "string" && Object.hasOwn(ERROR_COPY, candidate.code) && typeof candidate.operation === "string" && Object.hasOwn(OPERATION_LABEL, candidate.operation) && typeof candidate.action === "string" && typeof candidate.retryable === "boolean"; } export function presentGenerationError(error: PublicGenerationError): GenerationErrorPresentation { const copy = ERROR_COPY[error.code] ?? ERROR_COPY.unknown; return { ...copy(OPERATION_LABEL[error.operation] ?? "生成"), action: error.action, retryable: error.retryable, referenceId: error.reference_id }; } export function generationErrorText(error: unknown, fallback: string): string { if (!isPublicGenerationError(error)) return fallback; const presentation = presentGenerationError(error); return `${presentation.title}:${presentation.description}`; }