新增 friendlyFromApiError(apiError):兼容业务信封 {error:{code,message}}(ARCH §7.1)
与 FastAPI 422 {detail:[...]},422 映射为友好 VALIDATION 文案(不泄露技术细节)。
接入三处此前吞掉错因、永显通用文案的调用点:
- useOutline(QA #2):原 env.error.code 漏读 422 detail,永显「大纲生成失败」。
- ChainPage / ChainAdjudication(QA #11):原 friendlyError(undefined) 永显通用 toast,
现 surface 真实 code(如 LLM_UNAVAILABLE→去设置 provider / CONFLICT→提示裁决)。
+ messages.test.ts 加 3 例(业务码带动作 / 422→VALIDATION / 未知形兜底)。
门禁绿:vitest / tsc / eslint 干净。
66 lines
2.5 KiB
TypeScript
66 lines
2.5 KiB
TypeScript
// 错误码 → 友好中文文案(+ 可选跳转动作)。
|
||
// 对齐后端 ErrorCode(ARCH §7.1)+ 前端流式/网络码(useDraftStream)。
|
||
// 目的:UI 不直接暴露 error code,给作者可读的下一步。
|
||
|
||
export interface FriendlyError {
|
||
text: string;
|
||
// 可选的「下一步」链接(如未连模型 → 去设置)。
|
||
actionHref?: string;
|
||
actionLabel?: string;
|
||
}
|
||
|
||
const PROVIDER_ACTION = {
|
||
actionHref: "/settings/providers",
|
||
actionLabel: "去设置提供商",
|
||
} as const;
|
||
|
||
const MESSAGES: Record<string, FriendlyError> = {
|
||
// 后端 ErrorCode
|
||
LLM_UNAVAILABLE: {
|
||
text: "未连接可用模型,去设置里连一个 provider。",
|
||
...PROVIDER_ACTION,
|
||
},
|
||
RATE_LIMITED: { text: "请求过于频繁,稍后再试。" },
|
||
CONFLICT_UNRESOLVED: { text: "仍有未裁决的冲突,请先逐条裁决再验收。" },
|
||
NOT_FOUND: { text: "资源不存在或已被删除。" },
|
||
VALIDATION: { text: "提交内容有误,请检查后重试。" },
|
||
INTERNAL: { text: "服务出了点问题,请稍后重试。" },
|
||
// 前端流式/网络码
|
||
NETWORK: { text: "网络中断,请检查连接后重试。" },
|
||
STREAM_FAILED: { text: "写章中断,请重试。" },
|
||
};
|
||
|
||
const DEFAULT_TEXT = "出错了,请稍后重试。";
|
||
|
||
// 把 (code, 原始 message) 映射为友好文案。
|
||
// 已知码 → 预设文案 + 动作;未知码 → 回退到原始 message(非空)或通用兜底。
|
||
export function friendlyError(
|
||
code: string | undefined,
|
||
fallbackMessage?: string,
|
||
): FriendlyError {
|
||
if (code && MESSAGES[code]) {
|
||
return MESSAGES[code];
|
||
}
|
||
const fallback = fallbackMessage?.trim();
|
||
return { text: fallback && fallback.length > 0 ? fallback : DEFAULT_TEXT };
|
||
}
|
||
|
||
// 从原始 API 错误对象(openapi-fetch 的 `error`)提取友好文案。
|
||
// 兼容两种信封:业务错误 `{error:{code,message}}`(ARCH §7.1)与 FastAPI 校验错误 `{detail:[...]}`。
|
||
// 422 一律映射为友好的 VALIDATION 文案(不泄露技术性 detail)。未知形 → 通用兜底。
|
||
export function friendlyFromApiError(apiError: unknown): FriendlyError {
|
||
if (apiError && typeof apiError === "object") {
|
||
const env = apiError as {
|
||
error?: { code?: string; message?: string };
|
||
detail?: unknown;
|
||
};
|
||
if (env.error?.code) {
|
||
return friendlyError(env.error.code, env.error.message);
|
||
}
|
||
if (env.detail !== undefined) {
|
||
return friendlyError("VALIDATION");
|
||
}
|
||
}
|
||
return friendlyError(undefined);
|
||
}
|