Files
rootandClaude Opus 5.5 a9147e647d feat(downloads): fold download plans into download panel (DL-3)
- Waiting plans (approval / disk space / automation off / queued) are
  active-segment rows on /download-tasks/board, merged by created_at,
  counted, with approve / cancel actions; ?plan= deep links and legacy
  /downloads/plans routes redirect to the panel
- Download detail shows "why this download" (source, purpose, replaced
  version, reasons); raw evaluation in tech info
- Failed / cancelled / expired plans appear in the entity download
  history; rejected / revoked / expired approvals close draft plans
- New GET /acquisition-plans/{id}/why, POST /acquisition-plans/{id}/cancel
- User-facing copy no longer says 下载计划; glossary updated

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-30 01:15:25 +08:00

36 lines
1.6 KiB
TypeScript

import { looksLikeCode, reasonText } from "@umt/contracts";
// 原因串 → 一句人话(DEAI-2)。映射表在 @umt/contracts 的 reasons.ts,服务端与前端共用;
// 原码交给 <TechInfo> 显示,不直接给人看。
/** `{ code, message }`(整理记录 / 文件结果)或裸原因串 → 一句话;不认识的码返回 fallback */
export function reasonLine(
r: { code?: string | null; message?: string | null } | string | null | undefined,
fallback = "处理失败",
): string | null {
if (!r) return null;
const code = typeof r === "string" ? r : (r.code ?? null);
const message = typeof r === "string" ? null : (r.message ?? null);
// 服务端给的中文说明(冲突明细等)优先
if (message && !looksLikeCode(message) && message !== code) return message;
const t = reasonText(code) ?? reasonText(message);
if (t) return t.action ? `${t.text}。${t.action}` : t.text;
if (code && !looksLikeCode(code)) return code;
return fallback;
}
/** 原码(给技术信息用):只有是内部码时才返回 */
export function reasonCode(r: { code?: string | null; message?: string | null } | string | null | undefined) {
if (!r) return null;
if (typeof r === "string") return looksLikeCode(r) ? r : null;
const c = r.code ?? null;
const m = r.message ?? null;
return [c, m && looksLikeCode(m) && m !== c ? m : null].filter(Boolean).join(" · ") || null;
}
/** 原因串里的内部码替换成人话(如计划 statusReason、任务 waitReason) */
export function humanizeReason(v: string | null | undefined): string | null {
if (!v) return null;
return looksLikeCode(v) ? reasonLine(v, v) : v;
}