Files
cc-switch/src/lib/errors/skillErrorParser.ts
T
Jason 987fc46e06 feat(skills): add install from ZIP file feature
- Add open_zip_file_dialog command for selecting ZIP files
- Add install_from_zip service method with recursive skill scanning
- Add install_skills_from_zip Tauri command
- Add frontend API methods and useInstallSkillsFromZip hook
- Add "Install from ZIP" button in Skills management page
- Support local skill ID format: local:{directory}
- Add i18n translations for new feature and error messages
2026-01-29 10:09:45 +08:00

109 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { TFunction } from "i18next";
/**
* 结构化错误对象
*/
export interface SkillError {
code: string;
context: Record<string, string>;
suggestion?: string;
}
/**
* 尝试解析后端返回的错误字符串
* 如果是 JSON 格式,返回结构化错误;否则返回 null
*/
export function parseSkillError(errorString: string): SkillError | null {
try {
const parsed = JSON.parse(errorString);
if (parsed.code && parsed.context) {
return parsed as SkillError;
}
} catch {
// 不是 JSON 格式,返回 null
}
return null;
}
/**
* 将错误码映射到 i18n key
*/
function getErrorI18nKey(code: string): string {
const mapping: Record<string, string> = {
SKILL_NOT_FOUND: "skills.error.skillNotFound",
MISSING_REPO_INFO: "skills.error.missingRepoInfo",
DOWNLOAD_TIMEOUT: "skills.error.downloadTimeout",
DOWNLOAD_FAILED: "skills.error.downloadFailed",
SKILL_DIR_NOT_FOUND: "skills.error.skillDirNotFound",
SKILL_DIRECTORY_CONFLICT: "skills.error.directoryConflict",
EMPTY_ARCHIVE: "skills.error.emptyArchive",
GET_HOME_DIR_FAILED: "skills.error.getHomeDirFailed",
NO_SKILLS_IN_ZIP: "skills.error.noSkillsInZip",
};
return mapping[code] || "skills.error.unknownError";
}
/**
* 将建议码映射到 i18n key
*/
function getSuggestionI18nKey(suggestion: string): string {
const mapping: Record<string, string> = {
checkNetwork: "skills.error.suggestion.checkNetwork",
checkProxy: "skills.error.suggestion.checkProxy",
retryLater: "skills.error.suggestion.retryLater",
checkRepoUrl: "skills.error.suggestion.checkRepoUrl",
checkPermission: "skills.error.suggestion.checkPermission",
uninstallFirst: "skills.error.suggestion.uninstallFirst",
checkZipContent: "skills.error.suggestion.checkZipContent",
http403: "skills.error.http403",
http404: "skills.error.http404",
http429: "skills.error.http429",
};
return mapping[suggestion] || suggestion;
}
/**
* 格式化技能错误为用户友好的消息
* @param errorString 后端返回的错误字符串
* @param t i18next 翻译函数
* @param defaultTitle 默认标题的 i18n key(如 "skills.installFailed"
* @returns 包含标题和描述的对象
*/
export function formatSkillError(
errorString: string,
t: TFunction,
defaultTitle: string = "skills.installFailed",
): { title: string; description: string } {
const parsedError = parseSkillError(errorString);
if (!parsedError) {
// 如果不是结构化错误,返回原始错误字符串
return {
title: t(defaultTitle),
description: errorString || t("common.error"),
};
}
const { code, context, suggestion } = parsedError;
// 获取错误消息的 i18n key
const errorKey = getErrorI18nKey(code);
// 构建描述(错误消息 + 建议)
let description = t(errorKey, context);
// 如果有建议,追加到描述中
if (suggestion) {
const suggestionKey = getSuggestionI18nKey(suggestion);
const suggestionText = t(suggestionKey);
description += `\n\n${suggestionText}`;
}
return {
title: t(defaultTitle),
description,
};
}