ae3ab5feb5
- os/ 67 文件(DesktopOS/Dock/Launcher/MenuBar/MissionControl/NotificationCenter/AppStore/Window 系统 + os*Store/hooks) - pages/Hub + api/modules/hub.ts + ImportHubModal(已有) - Inbox 采用上游 Mailbox(approvals/messages 双 Tab + MailAccessControlDrawer/useMailPendingCount/mailDetail),保留云超服 Harvest 组件 - App.tsx 引入 OS Shell 分支(isOsPath→DesktopOSPage, Router 外)+ /hub/admin 路由 + BackendModeRouter + RuntimeAvailabilityGuard,保留云超服 AuthGuard/chatSocket/OPC 主题/vi-th-my-lo - 双改合并:usePluginLoader(loadPawApp)/pluginMarket(isMarketPluginApp)/registry store(removeBySource)/hostExternals(removePlugin)/LanguageSwitcher(persistRemotely+ja-ru-pt-id)/BackendLoadingPage(override props)/ApprovalCard(reasoning)/auth.ts(mode)/auth/gate.ts - SidebarSettingsPanel 加 Desktop Mode 入口(OS Shell 切换) - locales 合并 os/hub/projectDirectory/inbox/account(zh/en/vi 用上游,lo/my/th 用 en 占位) - 验证:tsc 0 错误、build 通过(1m23s)、阶段5 新增 35 文件 271 测试全过;全量回归 30 失败均预存在(stash 验证)
46 lines
1.4 KiB
TypeScript
46 lines
1.4 KiB
TypeScript
interface ValidationIssue {
|
|
loc?: unknown;
|
|
msg?: unknown;
|
|
}
|
|
|
|
function validationIssueMessage(issue: ValidationIssue): string | null {
|
|
if (typeof issue.msg !== "string" || !issue.msg) return null;
|
|
if (!Array.isArray(issue.loc)) return issue.msg;
|
|
const path = issue.loc
|
|
.filter((part) => part !== "body")
|
|
.filter((part): part is string | number =>
|
|
["string", "number"].includes(typeof part),
|
|
)
|
|
.join(".");
|
|
return path ? `${path}: ${issue.msg}` : issue.msg;
|
|
}
|
|
|
|
export function formatApiError(payload: unknown, fallback: string): string {
|
|
if (!payload || typeof payload !== "object") return fallback;
|
|
const detail = (payload as { detail?: unknown }).detail;
|
|
if (typeof detail === "string" && detail) return detail;
|
|
if (Array.isArray(detail)) {
|
|
const messages = detail
|
|
.map((issue) =>
|
|
issue && typeof issue === "object"
|
|
? validationIssueMessage(issue as ValidationIssue)
|
|
: null,
|
|
)
|
|
.filter((message): message is string => Boolean(message));
|
|
if (messages.length) return messages.join("; ");
|
|
}
|
|
if (detail && typeof detail === "object") {
|
|
const message = validationIssueMessage(detail as ValidationIssue);
|
|
if (message) return message;
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
export async function responseErrorMessage(
|
|
response: Response,
|
|
fallback: string,
|
|
): Promise<string> {
|
|
const payload: unknown = await response.json().catch(() => null);
|
|
return formatApiError(payload, fallback);
|
|
}
|