feat: add ZIP skill import with client-side extraction

Add import skill modal that accepts .zip files via drag-and-drop or
file picker, extracts them client-side using fflate, validates structure
and security constraints, then batch uploads via presigned URLs.

- Add fflate dependency for browser-side ZIP decompression
- Create zip-extract.ts with fflate filter API for validation
- Create zip-to-upload-tree.ts for BatchUploadNodeInput tree building
- Create import-skill-modal.tsx with drag-and-drop support
- Lazy-load ImportSkillModal via next/dynamic for bundle optimization
- Add en-US and zh-Hans i18n keys for import modal
This commit is contained in:
yyh
2026-01-30 21:49:45 +08:00
parent ea91f96924
commit ea88bcfbd2
8 changed files with 476 additions and 4 deletions

View File

@ -1081,6 +1081,21 @@
"skill.startTab.createModal.title": "Create Blank Skill",
"skill.startTab.createSuccess": "Skill \"{{name}}\" created successfully",
"skill.startTab.filesIncluded": "{{count}} files included",
"skill.startTab.importModal.browseFiles": "Browse Files",
"skill.startTab.importModal.changeFile": "Change File",
"skill.startTab.importModal.dropHint": "Drop a .zip file here, or",
"skill.startTab.importModal.errorEmptyZip": "ZIP file contains no files",
"skill.startTab.importModal.errorExtractedTooLarge": "Extracted content is too large",
"skill.startTab.importModal.errorInvalidZip": "Invalid ZIP file",
"skill.startTab.importModal.errorNoRootFolder": "ZIP must contain exactly one root folder",
"skill.startTab.importModal.errorPathTraversal": "ZIP contains unsafe file paths",
"skill.startTab.importModal.errorTooManyFiles": "ZIP contains too many files",
"skill.startTab.importModal.fileTooLarge": "ZIP file exceeds 50MB limit",
"skill.startTab.importModal.importButton": "Import",
"skill.startTab.importModal.importSuccess": "Skill \"{{name}}\" imported successfully",
"skill.startTab.importModal.invalidFileType": "Please select a .zip file",
"skill.startTab.importModal.nameDuplicate": "A skill with this name already exists",
"skill.startTab.importModal.title": "Import Skill",
"skill.startTab.importSkill": "Import Skill",
"skill.startTab.importSkillDesc": "Import skill from skill.zip file",
"skill.startTab.searchPlaceholder": "Search…",

View File

@ -1073,6 +1073,21 @@
"skill.startTab.createModal.title": "创建空白 Skill",
"skill.startTab.createSuccess": "Skill \"{{name}}\" 创建成功",
"skill.startTab.filesIncluded": "包含 {{count}} 个文件",
"skill.startTab.importModal.browseFiles": "浏览文件",
"skill.startTab.importModal.changeFile": "更换文件",
"skill.startTab.importModal.dropHint": "将 .zip 文件拖放到此处,或",
"skill.startTab.importModal.errorEmptyZip": "ZIP 文件中没有文件",
"skill.startTab.importModal.errorExtractedTooLarge": "解压后内容过大",
"skill.startTab.importModal.errorInvalidZip": "无效的 ZIP 文件",
"skill.startTab.importModal.errorNoRootFolder": "ZIP 必须包含且仅包含一个根文件夹",
"skill.startTab.importModal.errorPathTraversal": "ZIP 包含不安全的文件路径",
"skill.startTab.importModal.errorTooManyFiles": "ZIP 包含的文件数量过多",
"skill.startTab.importModal.fileTooLarge": "ZIP 文件超过 50MB 限制",
"skill.startTab.importModal.importButton": "导入",
"skill.startTab.importModal.importSuccess": "Skill \"{{name}}\" 导入成功",
"skill.startTab.importModal.invalidFileType": "请选择 .zip 文件",
"skill.startTab.importModal.nameDuplicate": "已存在同名 Skill",
"skill.startTab.importModal.title": "导入 Skill",
"skill.startTab.importSkill": "导入 Skill",
"skill.startTab.importSkillDesc": "从 skill.zip 文件导入",
"skill.startTab.searchPlaceholder": "搜索…",