From 4073edcc200a2805813e978bcfd55c53f813a07e Mon Sep 17 00:00:00 2001 From: Administrator Date: Sun, 13 Sep 2026 20:35:47 +0800 Subject: [PATCH] =?UTF-8?q?AI=20=E5=B0=8F=E8=AF=B4=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E5=8F=B0=EF=BC=9A=E5=88=86=E5=B1=82=E6=B5=81?= =?UTF-8?q?=E6=B0=B4=E7=BA=BF=20+=20=E6=95=85=E4=BA=8B=E5=9C=A3=E7=BB=8F?= =?UTF-8?q?=EF=BC=88=E4=B8=80=E6=9C=9F=E5=AE=8C=E6=95=B4=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端:Node/Express + node:sqlite,人物/世界观/分卷/细纲/正文 8 阶段生成流水线(SSE 流式 + zod 校验 + 重试) - 前端:React 18 + Vite + antd 三栏工作台:流水线引导、章节树、编辑器(快切章节/段落改写)、故事圣经面板 - 写作工艺注入:人物声口/内在冲突/秘密,细纲 POV/情绪曲线/功能/张力,正文专业规则 - 自动回写故事圣经(摘要/设定/人物变化/伏笔/时间线),TXT/EPUB 导出,token 成本日志 --- .env.example | 3 + .gitignore | 8 + README.md | 81 + package-lock.json | 4960 ++++++++++++++++++++++++ package.json | 17 + scripts/e2e_test.py | 106 + server/package.json | 24 + server/src/db/connection.ts | 16 + server/src/db/schema.ts | 135 + server/src/db/seed.ts | 39 + server/src/db/types.ts | 112 + server/src/index.ts | 52 + server/src/llm/client.ts | 162 + server/src/llm/prompts.ts | 362 ++ server/src/pipeline/context.ts | 108 + server/src/pipeline/postprocess.ts | 117 + server/src/routes/assets.ts | 137 + server/src/routes/chapters.ts | 82 + server/src/routes/export.ts | 52 + server/src/routes/generate.ts | 450 +++ server/src/routes/outline.ts | 119 + server/src/routes/projects.ts | 80 + server/src/routes/settings.ts | 46 + server/src/util/epub.ts | 103 + server/src/util/json.ts | 24 + server/src/util/sse.ts | 15 + server/src/util/words.ts | 4 + server/tsconfig.json | 14 + web/index.html | 12 + web/package.json | 24 + web/src/App.tsx | 27 + web/src/api/client.ts | 93 + web/src/api/types.ts | 147 + web/src/components/AutoSaveText.tsx | 48 + web/src/components/BeatsView.tsx | 150 + web/src/components/ChapterEditor.tsx | 298 ++ web/src/components/ChapterTree.tsx | 151 + web/src/components/GenerateDrawer.tsx | 136 + web/src/components/NewProjectModal.tsx | 77 + web/src/components/OutlineView.tsx | 71 + web/src/components/PipelineGuide.tsx | 130 + web/src/components/SettingsModal.tsx | 98 + web/src/components/StoryBible.tsx | 349 ++ web/src/global.css | 19 + web/src/main.tsx | 11 + web/src/pages/HomePage.tsx | 122 + web/src/pages/WorkbenchPage.tsx | 410 ++ web/tsconfig.json | 14 + web/vite.config.ts | 12 + 49 files changed, 9827 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/e2e_test.py create mode 100644 server/package.json create mode 100644 server/src/db/connection.ts create mode 100644 server/src/db/schema.ts create mode 100644 server/src/db/seed.ts create mode 100644 server/src/db/types.ts create mode 100644 server/src/index.ts create mode 100644 server/src/llm/client.ts create mode 100644 server/src/llm/prompts.ts create mode 100644 server/src/pipeline/context.ts create mode 100644 server/src/pipeline/postprocess.ts create mode 100644 server/src/routes/assets.ts create mode 100644 server/src/routes/chapters.ts create mode 100644 server/src/routes/export.ts create mode 100644 server/src/routes/generate.ts create mode 100644 server/src/routes/outline.ts create mode 100644 server/src/routes/projects.ts create mode 100644 server/src/routes/settings.ts create mode 100644 server/src/util/epub.ts create mode 100644 server/src/util/json.ts create mode 100644 server/src/util/sse.ts create mode 100644 server/src/util/words.ts create mode 100644 server/tsconfig.json create mode 100644 web/index.html create mode 100644 web/package.json create mode 100644 web/src/App.tsx create mode 100644 web/src/api/client.ts create mode 100644 web/src/api/types.ts create mode 100644 web/src/components/AutoSaveText.tsx create mode 100644 web/src/components/BeatsView.tsx create mode 100644 web/src/components/ChapterEditor.tsx create mode 100644 web/src/components/ChapterTree.tsx create mode 100644 web/src/components/GenerateDrawer.tsx create mode 100644 web/src/components/NewProjectModal.tsx create mode 100644 web/src/components/OutlineView.tsx create mode 100644 web/src/components/PipelineGuide.tsx create mode 100644 web/src/components/SettingsModal.tsx create mode 100644 web/src/components/StoryBible.tsx create mode 100644 web/src/global.css create mode 100644 web/src/main.tsx create mode 100644 web/src/pages/HomePage.tsx create mode 100644 web/src/pages/WorkbenchPage.tsx create mode 100644 web/tsconfig.json create mode 100644 web/vite.config.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ce14ce9 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +ARK_BASE_URL=https://ark.cn-beijing.volces.com/api/v3 +ARK_API_KEY=你的APIKey +ARK_MODEL=doubao-seed-1-6-flash diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a2e1e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +dist/ +server/data/ +web/dist/ +.env +*.log +.DS_Store +.zcode/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..09e0a45 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +# 智能小说工作台 + +从一句话创意到完整长篇的 AI 小说生成工作台。核心设计:**分层流水线 + 故事圣经**——AI 不会一次生成整本书,而是沿「人物卡 → 世界观 → 分卷大纲 → 分章细纲 → 逐章正文」推进,每一步产物都可编辑确认;正文生成时自动注入相关人物卡、前情摘要与未回收伏笔,写完后自动把摘要、新设定、人物变化、伏笔、时间线回写进"故事圣经",保证长篇的前后一致性。 + +## 功能 + +- **项目管理**:作品名 / 题材 / 故事创意 / 文风要求与示例 / 目标规模,卡片式总览,字数与进度统计 +- **人物卡 & 世界观**:AI 生成(可反复重生成),全部字段可编辑;生成正文后自动追加人物近况 +- **大纲与细纲**:AI 生成分卷大纲、每卷分章细纲(事件 / 涉及人物 / 伏笔安排),均可编辑 +- **逐章正文生成**:流式输出(SSE),可中断;上下文注入 = 本章细纲 + 涉及人物完整卡 + 相关设定 + 全部前章摘要 + 上一章结尾 500 字 + 未回收伏笔 +- **段落级改写**:选中任意段落重写 / 扩写 / 缩写 / 自定义指令,结果确认后替换 +- **故事圣经面板**:人物 / 世界观 / 伏笔(未回收·已回收)/ 时间线,右侧栏常驻可直接增删改 +- **自动回写**:每章正文采纳后,AI 自动提取章节摘要、新设定、人物状态变化、埋设/回收的伏笔、时间线事件 +- **导出**:TXT(含卷章结构)与 EPUB(EPUB 3,目录按卷分组) +- **生成日志**:记录每次调用的模型与 token 用量(`generation_logs` 表) + +## 快速开始 + +```bash +npm install # 已安装过可跳过 +npm run dev # 开发模式:API 127.0.0.1:5175 + 前端 127.0.0.1:5173(自动代理) +``` + +生产模式(单端口,服务同时托管前端页面): + +```bash +npm run build +npm start # 打开 http://127.0.0.1:5175 +``` + +数据存放在 `server/data/novel.db`(SQLite 单文件,WAL 模式),备份即复制该文件。 + +## 模型接入(重要) + +支持任意 **OpenAI 兼容接口**。首次启动会从 `.env` 读取初始配置,之后在界面右上角「模型设置」中查看修改,可一键「测试连接」。 + +当前 `.env` 预置了火山方舟配置: + +``` +ARK_BASE_URL=https://ark.cn-beijing.volces.com/api/plan/v3 +ARK_API_KEY=ark-xxxx # 你的 API Key +ARK_MODEL=doubao-seed-2.0-lite +``` + +当前 `.env` 已配置火山方舟 Agent Plan 并实测连通: + +``` +ARK_BASE_URL=https://ark.cn-beijing.volces.com/api/plan/v3 +ARK_API_KEY=ark-xxxx +ARK_MODEL=doubao-seed-2.0-lite +``` + +> 配置以界面「模型设置」中保存的为准(存在数据库里)。若要换模型,注意 Agent Plan 只支持套餐内模型(如 `doubao-seed-2.0-lite`),标准后付费模型名会报 UnsupportedModel。应用会自动关闭豆包 seed 系列的深度思考以提速并保证输出干净。 + +## 目录结构 + +``` +├── server/ # Node.js + Express + node:sqlite +│ └── src/ +│ ├── db/ # 建表 / 种子 / 行类型 +│ ├── routes/ # CRUD、生成(SSE)、导出、设置 +│ ├── llm/ # OpenAI 兼容客户端 + 各阶段提示词 +│ └── pipeline/ # 上下文组装 + 圣经回写 +├── web/ # React 18 + Vite + Ant Design +│ └── src/components/ # 章节树 / 编辑器 / 生成抽屉 / 故事圣经 等 +├── scripts/e2e_test.py # 端到端回归测试(python -X utf8 scripts/e2e_test.py,需服务已启动) +└── .env # 模型配置(已 gitignore,勿提交) +``` + +## 设计说明 + +- **为什么不是一键生成整本**:长篇的难点是一致性(人物/伏笔/设定),分层生成 + 中间产物人工确认,比"一把梭"质量高得多,也便宜得多(改大纲比重写正文便宜)。 +- **上下文策略**:MVP 采用"滚动摘要 + 相关实体注入",可稳定支撑数百章;向量检索留作二期增强。 +- **模型容错**:自动兼容豆包 seed 系列的 thinking 参数(400 时降级重试)、JSON 输出解析失败自动带反馈重试(最多 3 次)。 + +## 常见问题 + +- **生成报 401 / AuthenticationError**:API Key 无效,去「模型设置」更换。 +- **生成报 UnsupportedModel**:Agent Plan Key 只能用套餐内模型名(当前可用:`doubao-seed-2.0-lite`)。 +- **生成报 max_tokens 相关错误**:模型输出上限小于设置值,调小「单次最大输出 tokens」。 +- **端口号**:默认 API 5175、前端开发端口 5173,可在 `server/src/index.ts` / `web/vite.config.ts` 修改。 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b499490 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4960 @@ +{ + "name": "novel-workbench", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "novel-workbench", + "version": "0.1.0", + "workspaces": [ + "server", + "web" + ], + "devDependencies": { + "concurrently": "^9.1.0" + } + }, + "node_modules/@ant-design/colors": { + "version": "7.2.1", + "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-7.2.1.tgz", + "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "1.24.0", + "resolved": "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-1.24.0.tgz", + "integrity": "sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "classnames": "^2.3.1", + "csstype": "^3.1.3", + "rc-util": "^5.35.0", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz", + "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.6.0.tgz", + "integrity": "sha512-PRomU725ABMf/lnQp5HiB7my1kjEbFY0D10N4lXYxK6TIB1gKjIVD5MRThpDaezLgw1D774J8eOeeDB0M6wHrQ==", + "license": "MIT" + }, + "node_modules/@ant-design/react-slick": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "resize-observer-polyfill": "^1.5.1", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.1.2.tgz", + "integrity": "sha512-WYbrZSjzznU1ekD0qFq2qRxt309VoS61MTG5npnFQlKYcoy9IzU8T+ZCIhq5bGAXRbXysABFWTspicMfmWFwow==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6", + "@babel/runtime": "^7.23.6", + "classnames": "^2.2.6", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@rc-component/context/-/context-1.4.0.tgz", + "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/@rc-component/mini-decimal/-/mini-decimal-1.1.4.tgz", + "integrity": "sha512-xiuXcaCwyOWpD8a8scdExFl+bntNphAW8XeenL1ig2en0AAZY0Pcp4pC0dI22qJ+NvxKn9RoNIoRdqYU3BLH4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", + "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@rc-component/qrcode/-/qrcode-1.1.3.tgz", + "integrity": "sha512-aGv6alnn4HbDEsURzKP+jv13rbi1VxmAYfBNZr5GKF1iohMNWy5tAVoJ1E3cOvzMB1kbUPvCXchM6zSFlRGPhA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "1.15.1", + "resolved": "https://registry.npmmirror.com/@rc-component/tour/-/tour-1.15.1.tgz", + "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/portal": "^1.0.0-9", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/trigger": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-2.3.1.tgz", + "integrity": "sha512-ORENF39PeXTzM+gQEshuk460Z8N4+6DkjpxlpE7Q3gYy1iBpLrx0FOJz3h62ryrJZ/3zCAUIkT1Pb/8hHWpb3A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@rc-component/portal": "^1.1.0", + "classnames": "^2.3.2", + "rc-motion": "^2.0.0", + "rc-resize-observer": "^1.3.1", + "rc-util": "^5.44.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.4", + "resolved": "https://registry.npmmirror.com/@remix-run/router/-/router-1.23.4.tgz", + "integrity": "sha512-q7j5geK7xs3UJSdm9/iytUNclBnLmYx1EnSeCFXHPeutdqgIMeFeHtUZgS3EhlKxdBEAu8OwtJCwmLrEzpSs7Q==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.2.tgz", + "integrity": "sha512-Xa6RDoWa+hNiX6PgsljlH6W75RaONx3y6PVlbLhkEWW+GaPQ3dP5gwbL/erAzQHWwkvW5UxdD5l87Qx2FAQ/4A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.2.tgz", + "integrity": "sha512-vNASxsghMfQ5s+v3PrpnJd+ryL/26lxCCaGI+sDJ7VzmHiYXIrrVltsDhaawxLM1WcoMU2oYlbPHLaYQtBzhcg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.2.tgz", + "integrity": "sha512-0dWDjmlrpZAgjPD/aPzUDhBW8APLRjAni5bOrM76wiiZm+E+KTMVKNhAzaTBohz8UyO2fKNAl0+fygbe2HZXOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.2.tgz", + "integrity": "sha512-N58uktcwzk3+qT4KHEuNdIxX1N01RWrkfVoml69EAbSaNDL+sbNVLx2RMl4Qd23lpA0fgPvyh5hHb4weD5WKmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.2.tgz", + "integrity": "sha512-HWF2zH8EAp2scWRpt2PGe6iUGz7zi04waXsdRr3zb4DWCk2ImIo5FZu0jjmD53nP/DGSvnW0e7/1ToCNZs2lZw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.2.tgz", + "integrity": "sha512-MkvcwHMnzPSMOQEwB6wHnLzmc+hT8BGc5bW/Mhmjjgx3wbj6VBnlc47XsK74kD0K9MikFfXpQqyz4NUXaUW62A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.2.tgz", + "integrity": "sha512-xe1bCKPJaKsD0tfd7Rb6bGfUogJTpKbTEEthsfdb7hTfTRNJVQTdirabQx0o6ERVba/smkM720soMY+0QnrlSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.2.tgz", + "integrity": "sha512-yOM7LdK0p6gk6+Q773OEwtlsikT1TL3yMmYsTtRlDRPha5vV2DC5x7LqRWDr6f3cSYNMKVqxzffXv8ivxNBIFQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.2.tgz", + "integrity": "sha512-qiWuJJV3DybA2IfzvRimeKXGrGuVPv1zobSY/26KnP3HbV0VcNb3ECzgvtbvF3xjSMkcooou6HASXZuLdjnhpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.2.tgz", + "integrity": "sha512-akcZquRzCY/KpUoZAMBhGf7oi4LmXq1BzRA5CPAC3rkUf28Y/sAYV3jSL+JKd7cwEyFvR5G0XVZ0gaMedP+60A==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.2.tgz", + "integrity": "sha512-fNwYHrPyYyxauPzX/cpYw8Z7LQpp+DGA0KCoswA0aVFBpmdMil9XgjB8V3Ny64Ihu797+GKcuJqnsOKEmor7fA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.2.tgz", + "integrity": "sha512-XfvsgzR7DZqREdst7K1Mj3ilSUM5xLAHJcIMDFPKdxTs9q5VHOT8aMA+a683fqBu7DQl8+Sd9HCsQYL8EMY9qA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.2.tgz", + "integrity": "sha512-Pp7gVZggEFlbcuztay+/U0gVG9S1XAh8i7I1Re/htbAzo43P5wHZHw6pTyzotISqlKohoh9RpIfnOz3RbemK1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.2.tgz", + "integrity": "sha512-zkgL2xff6i7u5hau/m6FGeS8gRkLEdgLw522WGmdWWlLd9btmNl3S80mcEjtGq+kvgUekQ3+BOYLLLcPlS2LIA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.2.tgz", + "integrity": "sha512-qOheJomrkVCbbHFJ7L3J97cnhfogKqguAQphv26+3ZsAQIF1L19b+dArl//s8rjJHJLz9byykyM8NBP4nmSa1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.2.tgz", + "integrity": "sha512-XlxLD54wQhH3FciCgMofxBw27NzUe818gJH410qWvc41UT0ZFcgxVjyX5/EK8MPTupjeVWqN5oy+9pCA9mqfCA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.2.tgz", + "integrity": "sha512-vdryWeRb2bLJZf0Fv/W8se6nvsHe2PkTCxV0meheK3nQE+G90VCJcke51Miy1yQRsfm2uqIyjXOu4wmUzbTtkQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.2.tgz", + "integrity": "sha512-bcq2h2pkKmH2po4cZV8VWzO4lL40STyu/nLoFpYMQp9C2tCVNTdcVv86MwSsn3D5s1FBe2Ty1atqvVAUTMimNg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.2.tgz", + "integrity": "sha512-EGoo5DMVMRkTId8fuTDaoxVlR5ZTsKULUezRjd9gCw5eeY+DjCvDpZAOlNUvKPGX+7rS1RWx6j+yOpNPx0cUgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.2.tgz", + "integrity": "sha512-MErl12k7BFHZG1TI9QF/3lSSZARzq9KgNy/FjnqFMCkv+N4RSSzoUCA5h2mqHX4Mox3WaTVKblyzhQ1zRb2ZuQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.2.tgz", + "integrity": "sha512-ILs8k07Wh4p0PsNY4wYLEaXZKMOpVhrG5QDB0yHhGhuzOfDlnyHN6sflL4El/MpUP1y8uY2lUZrv4oBS6pTT3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.2.tgz", + "integrity": "sha512-hKgB3nz/TKD3Wv78XEsyXzQsNjvhOHmwKQTvXADGOyU/cIClZDO7DsoggbdmJDPGp5V80tA3Vfv61PaKTLH3LA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.2.tgz", + "integrity": "sha512-T4wf1mudIDxN8Q/CWIBJC1u5gQUc+r5mPvlwoSbIvNkyVTP2TAFeobEmst5AQ4gMyAz4sSByVdoTDfvTmGK/8g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.2.tgz", + "integrity": "sha512-tC3IY7qoaD9Ll3/8WJQn49j5V2f/NuI9S41NOE2iM5MPs3sPIvOkVToLcz/7Bz4pyF7PSvrtwu8I/pUrGOSecQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.2.tgz", + "integrity": "sha512-6NHnk/K3eq2ZFYcU1X8g67s9qIJRCOTT92gwLMVBp08dB2uuuwI1/Q/empzL2Bfr2f2WRLJVwpp90RmacQyFkw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmmirror.com/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmmirror.com/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmmirror.com/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.9", + "resolved": "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.9.tgz", + "integrity": "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.4", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.13.4.tgz", + "integrity": "sha512-YJ7EqCstVTzIr0fMr7qul/977en+pQHrfmuKIo6Zr9i75Be21dr3MovcfvGtyvi2HAUrRerWps5sMO9I7WaxDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmmirror.com/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmmirror.com/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmmirror.com/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/antd": { + "version": "5.29.3", + "resolved": "https://registry.npmmirror.com/antd/-/antd-5.29.3.tgz", + "integrity": "sha512-3DdbGCa9tWAJGcCJ6rzR8EJFsv2CtyEbkVabZE14pfgUHfCicWCj0/QzQVLDYg8CPfQk9BH7fHCoTXHTy7MP/A==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.2.1", + "@ant-design/cssinjs": "^1.23.0", + "@ant-design/cssinjs-utils": "^1.1.3", + "@ant-design/fast-color": "^2.0.6", + "@ant-design/icons": "^5.6.1", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.26.0", + "@rc-component/color-picker": "~2.0.1", + "@rc-component/mutate-observer": "^1.1.0", + "@rc-component/qrcode": "~1.1.0", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.3.0", + "classnames": "^2.5.1", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.11", + "rc-cascader": "~3.34.0", + "rc-checkbox": "~3.5.0", + "rc-collapse": "~3.9.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.3.0", + "rc-dropdown": "~4.2.1", + "rc-field-form": "~2.7.1", + "rc-image": "~7.12.0", + "rc-input": "~1.8.0", + "rc-input-number": "~9.5.0", + "rc-mentions": "~2.20.0", + "rc-menu": "~9.16.1", + "rc-motion": "^2.9.5", + "rc-notification": "~5.6.4", + "rc-pagination": "~5.1.0", + "rc-picker": "~4.11.3", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.1", + "rc-resize-observer": "^1.4.3", + "rc-segmented": "~2.7.0", + "rc-select": "~14.16.8", + "rc-slider": "~11.1.9", + "rc-steps": "~6.0.1", + "rc-switch": "~4.1.0", + "rc-table": "~7.54.0", + "rc-tabs": "~15.7.0", + "rc-textarea": "~1.10.2", + "rc-tooltip": "~6.4.0", + "rc-tree": "~5.13.1", + "rc-tree-select": "~5.27.0", + "rc-upload": "~4.11.0", + "rc-util": "^5.44.4", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.23", + "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.23.tgz", + "integrity": "sha512-le521dGVfxM7yRX0EikCoSz+rOK+hHzdDt/E7mG1jOJB/6WAAUuwVroLwaB7ApaUsz5Q0kFlDXLSA9MheUIfRQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.8", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.8.tgz", + "integrity": "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.16.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/browserslist": { + "version": "4.28.9", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.9.tgz", + "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.20", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.420", + "node-releases": "^2.0.54", + "update-browserslist-db": "^1.3.2" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.4", + "resolved": "https://registry.npmmirror.com/concurrently/-/concurrently-9.2.4.tgz", + "integrity": "sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.9.0", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.23", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.427", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.427.tgz", + "integrity": "sha512-n14zb3FdsChZ2BNobqNHAJMcP3ifFv4paox2LvCrfVAQcqGiSURgbJl+PfMpHVCNFkStnNc+RRVtPBTVW5PDgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmmirror.com/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jszip": { + "version": "3.10.2", + "resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.2.tgz", + "integrity": "sha512-3l+rb15IOWtUhU0H5MFqES/T6Kh7abYwjosBey/vD6hDt8zoEffkSC5Ws5SGtgVw3gBx2NEbhTeSW1+kWkpyTQ==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-releases": { + "version": "2.0.55", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.55.tgz", + "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc-cascader": { + "version": "3.34.0", + "resolved": "https://registry.npmmirror.com/rc-cascader/-/rc-cascader-3.34.0.tgz", + "integrity": "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "^2.3.1", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-checkbox": { + "version": "3.5.0", + "resolved": "https://registry.npmmirror.com/rc-checkbox/-/rc-checkbox-3.5.0.tgz", + "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.25.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse": { + "version": "3.9.0", + "resolved": "https://registry.npmmirror.com/rc-collapse/-/rc-collapse-3.9.0.tgz", + "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "9.6.0", + "resolved": "https://registry.npmmirror.com/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/portal": "^1.0.0-8", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/rc-drawer/-/rc-drawer-7.3.0.tgz", + "integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@rc-component/portal": "^1.1.1", + "classnames": "^2.2.6", + "rc-motion": "^2.6.1", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dropdown": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/rc-dropdown/-/rc-dropdown-4.2.1.tgz", + "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-util": "^5.44.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-field-form": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-2.7.1.tgz", + "integrity": "sha512-vKeSifSJ6HoLaAB+B8aq/Qgm8a3dyxROzCtKNCsBQgiverpc4kWDQihoUwzUj+zNWJOykwSY4dNX3QrGwtVb9A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/async-validator": "^5.0.3", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-image": { + "version": "7.12.0", + "resolved": "https://registry.npmmirror.com/rc-image/-/rc-image-7.12.0.tgz", + "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/portal": "^1.0.2", + "classnames": "^2.2.6", + "rc-dialog": "~9.6.0", + "rc-motion": "^2.6.2", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/rc-input/-/rc-input-1.8.0.tgz", + "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.18.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-input-number": { + "version": "9.5.0", + "resolved": "https://registry.npmmirror.com/rc-input-number/-/rc-input-number-9.5.0.tgz", + "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/mini-decimal": "^1.0.1", + "classnames": "^2.2.5", + "rc-input": "~1.8.0", + "rc-util": "^5.40.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions": { + "version": "2.20.0", + "resolved": "https://registry.npmmirror.com/rc-mentions/-/rc-mentions-2.20.0.tgz", + "integrity": "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.22.5", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-input": "~1.8.0", + "rc-menu": "~9.16.0", + "rc-textarea": "~1.10.0", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.16.1", + "resolved": "https://registry.npmmirror.com/rc-menu/-/rc-menu-9.16.1.tgz", + "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.3.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "resolved": "https://registry.npmmirror.com/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.44.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-notification": { + "version": "5.6.4", + "resolved": "https://registry.npmmirror.com/rc-notification/-/rc-notification-5.6.4.tgz", + "integrity": "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.9.0", + "rc-util": "^5.20.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/rc-overflow/-/rc-overflow-1.5.0.tgz", + "integrity": "sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.37.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-pagination": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/rc-pagination/-/rc-pagination-5.1.0.tgz", + "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker": { + "version": "4.11.3", + "resolved": "https://registry.npmmirror.com/rc-picker/-/rc-picker-4.11.3.tgz", + "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.43.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/rc-progress": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate": { + "version": "2.13.1", + "resolved": "https://registry.npmmirror.com/rc-rate/-/rc-rate-2.13.1.tgz", + "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", + "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.44.1", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/rc-segmented/-/rc-segmented-2.7.1.tgz", + "integrity": "sha512-izj1Nw/Dw2Vb7EVr+D/E9lUTkBe+kKC+SAFSU9zqr7WV2W5Ktaa9Gc7cB2jTqgk8GROJayltaec+DBlYKc6d+g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-motion": "^2.4.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-select": { + "version": "14.16.8", + "resolved": "https://registry.npmmirror.com/rc-select/-/rc-select-14.16.8.tgz", + "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.1.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.3.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-slider": { + "version": "11.1.9", + "resolved": "https://registry.npmmirror.com/rc-slider/-/rc-slider-11.1.9.tgz", + "integrity": "sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/rc-steps/-/rc-steps-6.0.1.tgz", + "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.16.7", + "classnames": "^2.2.3", + "rc-util": "^5.16.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/rc-switch/-/rc-switch-4.1.0.tgz", + "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "classnames": "^2.2.1", + "rc-util": "^5.30.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table": { + "version": "7.54.0", + "resolved": "https://registry.npmmirror.com/rc-table/-/rc-table-7.54.0.tgz", + "integrity": "sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/context": "^1.4.0", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.44.3", + "rc-virtual-list": "^3.14.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "15.7.0", + "resolved": "https://registry.npmmirror.com/rc-tabs/-/rc-tabs-15.7.0.tgz", + "integrity": "sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.6.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.34.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea": { + "version": "1.10.2", + "resolved": "https://registry.npmmirror.com/rc-textarea/-/rc-textarea-1.10.2.tgz", + "integrity": "sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-input": "~1.8.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tooltip": { + "version": "6.4.0", + "resolved": "https://registry.npmmirror.com/rc-tooltip/-/rc-tooltip-6.4.0.tgz", + "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.1", + "rc-util": "^5.44.3" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tree": { + "version": "5.13.1", + "resolved": "https://registry.npmmirror.com/rc-tree/-/rc-tree-5.13.1.tgz", + "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select": { + "version": "5.27.0", + "resolved": "https://registry.npmmirror.com/rc-tree-select/-/rc-tree-select-5.27.0.tgz", + "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "2.x", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-upload": { + "version": "4.11.0", + "resolved": "https://registry.npmmirror.com/rc-upload/-/rc-upload-4.11.0.tgz", + "integrity": "sha512-ZUyT//2JAehfHzjWowqROcwYJKnZkIUGWaTE/VogVrepSl7AFNbQf4+zGfX4zl9Vrj/Jm8scLO0R6UlPDKK4wA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.44.4", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-virtual-list": { + "version": "3.19.2", + "resolved": "https://registry.npmmirror.com/rc-virtual-list/-/rc-virtual-list-3.19.2.tgz", + "integrity": "sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.6", + "resolved": "https://registry.npmmirror.com/react-router/-/react-router-6.30.6.tgz", + "integrity": "sha512-5HfK7k5im7LTOB0EqCQmfvy4C13G92Ssj1VTmouTK3AJvyjKTnFuCV0vcMAD/JS+JC4DvDIBRrlAeJIFjh5VWg==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.6", + "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.30.6.tgz", + "integrity": "sha512-0RHKZz7wwffvkU+2MFVT2NnjK44ssLEV+m0CAJaS2Ksmorrwj7WxH00jO0SOCW26/tINUnJHToXblDs33I38YQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.4", + "react-router": "6.30.6" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.63.2.tgz", + "integrity": "sha512-l5eyksV4tPBj6lJyEa37YzIOCSOV7lkZzEHUdpjWZbtD7wTcFYmEYXSgm5bT4vV+dZLb9rBG1W9GROOG4NS4Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.2", + "@rollup/rollup-android-arm64": "4.63.2", + "@rollup/rollup-darwin-arm64": "4.63.2", + "@rollup/rollup-darwin-x64": "4.63.2", + "@rollup/rollup-freebsd-arm64": "4.63.2", + "@rollup/rollup-freebsd-x64": "4.63.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.2", + "@rollup/rollup-linux-arm-musleabihf": "4.63.2", + "@rollup/rollup-linux-arm64-gnu": "4.63.2", + "@rollup/rollup-linux-arm64-musl": "4.63.2", + "@rollup/rollup-linux-loong64-gnu": "4.63.2", + "@rollup/rollup-linux-loong64-musl": "4.63.2", + "@rollup/rollup-linux-ppc64-gnu": "4.63.2", + "@rollup/rollup-linux-ppc64-musl": "4.63.2", + "@rollup/rollup-linux-riscv64-gnu": "4.63.2", + "@rollup/rollup-linux-riscv64-musl": "4.63.2", + "@rollup/rollup-linux-s390x-gnu": "4.63.2", + "@rollup/rollup-linux-x64-gnu": "4.63.2", + "@rollup/rollup-linux-x64-musl": "4.63.2", + "@rollup/rollup-openbsd-x64": "4.63.2", + "@rollup/rollup-openharmony-arm64": "4.63.2", + "@rollup/rollup-win32-arm64-msvc": "4.63.2", + "@rollup/rollup-win32-ia32-msvc": "4.63.2", + "@rollup/rollup-win32-x64-gnu": "4.63.2", + "@rollup/rollup-win32-x64-msvc": "4.63.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmmirror.com/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/server": { + "resolved": "server", + "link": true + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shell-quote": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.23.13", + "resolved": "https://registry.npmmirror.com/tsx/-/tsx-4.23.13.tgz", + "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/web": { + "resolved": "web", + "link": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "server": { + "dependencies": { + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.21.2", + "jszip": "^3.10.1", + "zod": "^3.24.1" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/node": "^24.0.0", + "tsx": "^4.19.2", + "typescript": "^5.7.2" + } + }, + "web": { + "dependencies": { + "antd": "^5.22.2", + "dayjs": "^1.11.13", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.7.2", + "vite": "^6.0.3" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..eeef9cb --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "novel-workbench", + "private": true, + "version": "0.1.0", + "workspaces": [ + "server", + "web" + ], + "scripts": { + "dev": "concurrently -n server,web -c blue,green \"npm run dev -w server\" \"npm run dev -w web\"", + "build": "npm run build -w server && npm run build -w web", + "start": "npm run start -w server" + }, + "devDependencies": { + "concurrently": "^9.1.0" + } +} diff --git a/scripts/e2e_test.py b/scripts/e2e_test.py new file mode 100644 index 0000000..1cca8ca --- /dev/null +++ b/scripts/e2e_test.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +"""端到端回归:纯 UTF-8 中文数据走完整 CRUD + 导出链路""" +import json +import urllib.request +import zipfile +import io +import sys + +BASE = 'http://127.0.0.1:5175' +FAILED = [] + + +def req(path, method='GET', body=None, raw=False): + data = json.dumps(body, ensure_ascii=False).encode('utf-8') if body is not None else None + r = urllib.request.Request(BASE + path, data=data, method=method) + if data: + r.add_header('Content-Type', 'application/json') + out = urllib.request.urlopen(r, timeout=30).read() + return out if raw else json.loads(out.decode('utf-8')) + + +def check(name, cond): + print(('PASS ' if cond else 'FAIL ') + name) + if not cond: + FAILED.append(name) + + +# 1. 建项目 +p = req('/api/projects', 'POST', { + 'name': '星海谎言', 'genre': '科幻', + 'premise': '世代飞船上的工程师发现航行早已结束,航行本身是统治阶层的谎言', + 'style_notes': '冷峻克制,多对话', 'target_chapter_count': 12, 'target_chapter_words': 2000, +}) +pid = p['id'] +check('project name roundtrip', p['name'] == '星海谎言') +check('premise roundtrip', p['premise'].startswith('世代飞船')) + +# 2. 人物 / 世界观 +c = req(f'/api/projects/{pid}/characters', 'POST', { + 'name': '林原', 'aliases': '老林', 'role': '主角', + 'personality': '沉稳多疑,对机械有近乎信仰的执着', 'goals': '查明航线数据的真相', + 'appearance': '瘦高,左眼有一道旧疤', 'background': '三代轮机世家,父亲死于一次"意外"检修事故', +}) +check('character roundtrip', c['name'] == '林原' and c['background'].startswith('三代轮机世家')) +cid = c['id'] +w = req(f'/api/projects/{pid}/world', 'POST', { + 'category': '地理', 'title': '世代飞船远望号', 'content': '全长十二公里,自持循环设计寿命五百年,实际已航行四百九十七年', +}) +check('world roundtrip', w['title'] == '世代飞船远望号') + +# 3. 卷 / 细纲 +v = req(f'/api/projects/{pid}/volumes', 'POST', {'title': '第一卷 裂缝', 'summary': '发现数据异常到确认真相', 'sort': 1}) +check('volume roundtrip', v['title'] == '第一卷 裂缝') +b = req(f'/api/projects/{pid}/beats', 'POST', { + 'volume_id': v['id'], 'title': '第1章 异响', 'events': '林原在检修中发现被删除四百九十七次的航线修正记录', + 'involved_characters': [cid], 'foreshadow_plan': '埋下船长知情暗示', +}) +check('beat involved ids parsed to array', b['involved_characters'] == [cid]) + +# 4. 章节 + 内容 + 字数 +ch = req(f'/api/projects/{pid}/chapters', 'POST', { + 'beat_id': b['id'], 'volume_id': v['id'], 'title': '第1章 异响', 'sort': 1, +}) +chid = ch['id'] +content = '检修通道的灯又灭了。林原把手电咬在嘴里,手指穿过线缆,摸到了那块不该存在的芯片。\n\n数据像潮水一样涌进终端——航线修正记录,五百年间,被删除了四百九十七次。' +updated = req(f'/api/chapters/{chid}', 'PATCH', {'content': content, 'status': 'edited'}) +check('word count correct', updated['word_count'] == len([ch for ch in content if not ch.isspace()])) + +# 5. bundle 聚合 +bundle = req(f'/api/projects/{pid}/bundle') +check('bundle beat involved is array', bundle['beats'][0]['involved_characters'] == [cid]) +check('bundle counts', len(bundle['characters']) == 1 and len(bundle['world']) == 1 and len(bundle['chapters']) == 1) + +# 6. 项目列表统计 +lst = req('/api/projects') +me = [x for x in lst if x['id'] == pid][0] +check('project stats', me['total_words'] > 0 and me['written_count'] == 1) + +# 7. 导出 TXT / EPUB +txt = req(f'/api/export/{pid}/txt', raw=True).decode('utf-8-sig') +check('txt contains title/body/volume', '星海谎言' in txt and '不该存在的芯片' in txt and '第一卷 裂缝' in txt) +epub_raw = req(f'/api/export/{pid}/epub', raw=True) +z = zipfile.ZipFile(io.BytesIO(epub_raw)) +check('epub valid zip', z.testzip() is None) +check('epub mimetype stored first', z.getinfo('mimetype').compress_type == zipfile.ZIP_STORED) +nav = z.read('OEBPS/nav.xhtml').decode('utf-8') +ch1 = z.read('OEBPS/chapter_0001.xhtml').decode('utf-8') +check('epub nav contains chapter', '第1章 异响' in nav) +check('epub chapter content escaped+present', '不该存在的芯片' in ch1) + +# 8. 生成接口的 SSE 错误路径:临时换成无效 key,应返回规范 error 事件而非崩溃,然后恢复原配置 +try: + saved = req('/api/settings') + req('/api/settings', 'PUT', {**saved, 'api_key': 'sk-invalid-key-for-e2e'}) + r = urllib.request.Request(BASE + '/api/generate/prose', data=json.dumps({ + 'projectId': pid, 'chapterId': chid + }).encode(), method='POST', headers={'Content-Type': 'application/json'}) + body = urllib.request.urlopen(r, timeout=60).read().decode('utf-8') + check('SSE error event emitted for auth failure', 'event: error' in body and 'HTTP' in body) +finally: + req('/api/settings', 'PUT', saved) + check('llm config restored', req('/api/settings')['api_key'] == saved['api_key']) + +print() +print('TOTAL FAILED:', len(FAILED)) +sys.exit(1 if FAILED else 0) diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..e83fb21 --- /dev/null +++ b/server/package.json @@ -0,0 +1,24 @@ +{ + "name": "server", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc -p tsconfig.json", + "start": "node dist/index.js" + }, + "dependencies": { + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.21.2", + "jszip": "^3.10.1", + "zod": "^3.24.1" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/node": "^24.0.0", + "tsx": "^4.19.2", + "typescript": "^5.7.2" + } +} diff --git a/server/src/db/connection.ts b/server/src/db/connection.ts new file mode 100644 index 0000000..106b44b --- /dev/null +++ b/server/src/db/connection.ts @@ -0,0 +1,16 @@ +import { DatabaseSync } from 'node:sqlite' +import { mkdirSync } from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +// 数据文件固定放在 server/data/ 下,与源码/编译产物位置无关 +const dataDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../data') + +export const db: DatabaseSync = (() => { + mkdirSync(dataDir, { recursive: true }) + const db = new DatabaseSync(path.join(dataDir, 'novel.db')) + db.exec('PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON;') + return db +})() + +export const now = () => new Date().toISOString() diff --git a/server/src/db/schema.ts b/server/src/db/schema.ts new file mode 100644 index 0000000..398a2ca --- /dev/null +++ b/server/src/db/schema.ts @@ -0,0 +1,135 @@ +import { db } from './connection.js' + +export function migrate() { + db.exec(` + CREATE TABLE IF NOT EXISTS llm_config ( + id INTEGER PRIMARY KEY CHECK (id = 1), + base_url TEXT NOT NULL, + api_key TEXT NOT NULL DEFAULT '', + model TEXT NOT NULL, + temperature REAL NOT NULL DEFAULT 0.8, + max_tokens INTEGER NOT NULL DEFAULT 12000, + updated_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS projects ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + genre TEXT NOT NULL DEFAULT '', + premise TEXT NOT NULL DEFAULT '', + style_notes TEXT NOT NULL DEFAULT '', + style_sample TEXT NOT NULL DEFAULT '', + target_chapter_words INTEGER NOT NULL DEFAULT 3000, + target_chapter_count INTEGER NOT NULL DEFAULT 30, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS characters ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + name TEXT NOT NULL, + aliases TEXT NOT NULL DEFAULT '', + role TEXT NOT NULL DEFAULT '配角', + appearance TEXT NOT NULL DEFAULT '', + personality TEXT NOT NULL DEFAULT '', + background TEXT NOT NULL DEFAULT '', + goals TEXT NOT NULL DEFAULT '', + arc_notes TEXT NOT NULL DEFAULT '', + sort INTEGER NOT NULL DEFAULT 0, + updated_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS world_entries ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + category TEXT NOT NULL DEFAULT '其他', + title TEXT NOT NULL, + content TEXT NOT NULL DEFAULT '', + sort INTEGER NOT NULL DEFAULT 0, + updated_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS volumes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + title TEXT NOT NULL, + summary TEXT NOT NULL DEFAULT '', + sort INTEGER NOT NULL DEFAULT 0 + ); + + CREATE TABLE IF NOT EXISTS beats ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + volume_id INTEGER REFERENCES volumes(id) ON DELETE SET NULL, + sort INTEGER NOT NULL DEFAULT 0, + title TEXT NOT NULL, + events TEXT NOT NULL DEFAULT '', + involved_characters TEXT NOT NULL DEFAULT '[]', + foreshadow_plan TEXT NOT NULL DEFAULT '' + ); + + CREATE TABLE IF NOT EXISTS chapters ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + beat_id INTEGER REFERENCES beats(id) ON DELETE SET NULL, + volume_id INTEGER REFERENCES volumes(id) ON DELETE SET NULL, + sort INTEGER NOT NULL DEFAULT 0, + title TEXT NOT NULL, + content TEXT NOT NULL DEFAULT '', + status TEXT NOT NULL DEFAULT 'empty', + summary TEXT NOT NULL DEFAULT '', + word_count INTEGER NOT NULL DEFAULT 0, + updated_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS foreshadows ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + description TEXT NOT NULL, + planted_chapter_id INTEGER REFERENCES chapters(id) ON DELETE SET NULL, + paid_chapter_id INTEGER REFERENCES chapters(id) ON DELETE SET NULL, + status TEXT NOT NULL DEFAULT 'planted', + notes TEXT NOT NULL DEFAULT '', + updated_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS timeline_events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + chapter_id INTEGER REFERENCES chapters(id) ON DELETE SET NULL, + story_time TEXT NOT NULL DEFAULT '', + title TEXT NOT NULL, + description TEXT NOT NULL DEFAULT '', + sort INTEGER NOT NULL DEFAULT 0 + ); + + CREATE TABLE IF NOT EXISTS generation_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE, + stage TEXT NOT NULL, + model TEXT NOT NULL DEFAULT '', + prompt_tokens INTEGER NOT NULL DEFAULT 0, + completion_tokens INTEGER NOT NULL DEFAULT 0, + ok INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL + ); + `) + + // 增量迁移:为旧库补齐新增列(写作专业字段) + const addColumns: [string, string, string][] = [ + ['projects', 'narrative_pov', "ALTER TABLE projects ADD COLUMN narrative_pov TEXT NOT NULL DEFAULT ''"], + ['projects', 'taboos', "ALTER TABLE projects ADD COLUMN taboos TEXT NOT NULL DEFAULT ''"], + ['characters', 'speech_pattern', "ALTER TABLE characters ADD COLUMN speech_pattern TEXT NOT NULL DEFAULT ''"], + ['characters', 'inner_conflict', "ALTER TABLE characters ADD COLUMN inner_conflict TEXT NOT NULL DEFAULT ''"], + ['characters', 'secret', "ALTER TABLE characters ADD COLUMN secret TEXT NOT NULL DEFAULT ''"], + ['beats', 'pov', "ALTER TABLE beats ADD COLUMN pov TEXT NOT NULL DEFAULT ''"], + ['beats', 'emotional_arc', "ALTER TABLE beats ADD COLUMN emotional_arc TEXT NOT NULL DEFAULT ''"], + ['beats', 'purpose', "ALTER TABLE beats ADD COLUMN purpose TEXT NOT NULL DEFAULT ''"], + ['beats', 'tension', 'ALTER TABLE beats ADD COLUMN tension INTEGER NOT NULL DEFAULT 0'] + ] + for (const [table, col, ddl] of addColumns) { + const cols = db.prepare(`PRAGMA table_info(${table})`).all() as unknown as { name: string }[] + if (!cols.some((c) => c.name === col)) db.exec(ddl) + } +} diff --git a/server/src/db/seed.ts b/server/src/db/seed.ts new file mode 100644 index 0000000..21fb995 --- /dev/null +++ b/server/src/db/seed.ts @@ -0,0 +1,39 @@ +import dotenv from 'dotenv' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { db, now } from './connection.js' + +// 从 src/db 或 dist/db 出发向上 3 层都是项目根目录 +const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..') +dotenv.config({ path: path.join(rootDir, '.env') }) + +export interface LlmConfig { + base_url: string + api_key: string + model: string + temperature: number + max_tokens: number +} + +export function seedLlmConfig() { + const row = db.prepare('SELECT id FROM llm_config WHERE id = 1').get() + if (row) return + db.prepare( + 'INSERT INTO llm_config (id, base_url, api_key, model, temperature, max_tokens, updated_at) VALUES (1, ?, ?, ?, ?, ?, ?)' + ).run( + process.env.ARK_BASE_URL || 'https://ark.cn-beijing.volces.com/api/v3', + process.env.ARK_API_KEY || '', + process.env.ARK_MODEL || 'doubao-seed-1-6-flash', + 0.8, + 12000, + now() + ) +} + +export function getLlmConfig(): LlmConfig { + const row = db.prepare('SELECT base_url, api_key, model, temperature, max_tokens FROM llm_config WHERE id = 1').get() as + | LlmConfig + | undefined + if (!row) throw new Error('LLM 配置缺失,请先启动初始化') + return row +} diff --git a/server/src/db/types.ts b/server/src/db/types.ts new file mode 100644 index 0000000..d12930e --- /dev/null +++ b/server/src/db/types.ts @@ -0,0 +1,112 @@ +export interface ProjectRow { + id: number + name: string + genre: string + premise: string + style_notes: string + style_sample: string + narrative_pov: string + taboos: string + target_chapter_words: number + target_chapter_count: number + created_at: string + updated_at: string +} + +export interface CharacterRow { + id: number + project_id: number + name: string + aliases: string + role: string + appearance: string + personality: string + background: string + goals: string + speech_pattern: string + inner_conflict: string + secret: string + arc_notes: string + sort: number + updated_at: string +} + +export interface WorldEntryRow { + id: number + project_id: number + category: string + title: string + content: string + sort: number + updated_at: string +} + +export interface VolumeRow { + id: number + project_id: number + title: string + summary: string + sort: number +} + +export interface BeatRow { + id: number + project_id: number + volume_id: number | null + sort: number + title: string + events: string + involved_characters: string // JSON array of character ids(数据库原始存储) + foreshadow_plan: string + pov: string + emotional_arc: string + purpose: string + tension: number +} + +/** 出站形态:involved_characters 已解析为 id 数组 */ +export type BeatParsed = Omit & { involved_characters: number[] } + +export interface ChapterRow { + id: number + project_id: number + beat_id: number | null + volume_id: number | null + sort: number + title: string + content: string + status: string + summary: string + word_count: number + updated_at: string +} + +export interface ForeshadowRow { + id: number + project_id: number + description: string + planted_chapter_id: number | null + paid_chapter_id: number | null + status: string + notes: string + updated_at: string +} + +export interface TimelineEventRow { + id: number + project_id: number + chapter_id: number | null + story_time: string + title: string + description: string + sort: number +} + +export function parseInvolved(json: string): number[] { + try { + const arr = JSON.parse(json) + return Array.isArray(arr) ? arr.map(Number).filter((n) => Number.isInteger(n)) : [] + } catch { + return [] + } +} diff --git a/server/src/index.ts b/server/src/index.ts new file mode 100644 index 0000000..5e5f209 --- /dev/null +++ b/server/src/index.ts @@ -0,0 +1,52 @@ +import express from 'express' +import cors from 'cors' +import path from 'node:path' +import { existsSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import { migrate } from './db/schema.js' +import { seedLlmConfig } from './db/seed.js' +import { projectsRouter } from './routes/projects.js' +import { assetsRouter } from './routes/assets.js' +import { outlineRouter } from './routes/outline.js' +import { chaptersRouter } from './routes/chapters.js' +import { settingsRouter } from './routes/settings.js' +import { generateRouter } from './routes/generate.js' +import { exportRouter } from './routes/export.js' + +migrate() +seedLlmConfig() + +const app = express() +app.use(cors()) +app.use(express.json({ limit: '10mb' })) + +app.use('/api', projectsRouter) +app.use('/api', assetsRouter) +app.use('/api', outlineRouter) +app.use('/api', chaptersRouter) +app.use('/api', settingsRouter) +app.use('/api', generateRouter) +app.use('/api', exportRouter) + +app.use('/api', (_req, res) => res.status(404).json({ error: '接口不存在' })) + +// 生产模式:托管前端构建产物(SPA 回退) +const webDist = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../web/dist') +if (existsSync(webDist)) { + app.use(express.static(webDist)) + app.use((req, res, next) => { + if (req.method === 'GET' && !req.path.startsWith('/api')) return res.sendFile(path.join(webDist, 'index.html')) + next() + }) +} + +// 统一错误处理 +app.use((err: unknown, _req: express.Request, res: express.Response, _next: express.NextFunction) => { + const msg = err instanceof Error ? err.message : String(err) + res.status(500).json({ error: msg }) +}) + +const PORT = Number(process.env.PORT || 5175) +app.listen(PORT, '127.0.0.1', () => { + console.log(`[novel-workbench] API 服务已启动: http://127.0.0.1:${PORT}`) +}) diff --git a/server/src/llm/client.ts b/server/src/llm/client.ts new file mode 100644 index 0000000..6a8107e --- /dev/null +++ b/server/src/llm/client.ts @@ -0,0 +1,162 @@ +import type { LlmConfig } from '../db/seed.js' + +export interface ChatMessage { + role: 'system' | 'user' | 'assistant' + content: string +} + +export interface Usage { + prompt_tokens?: number + completion_tokens?: number +} + +export interface ChatOpts { + onDelta?: (t: string) => void + signal?: AbortSignal + temperature?: number + maxTokens?: number +} + +const joinUrl = (base: string, p: string) => base.replace(/\/+$/, '') + p + +function buildBody(cfg: LlmConfig, messages: ChatMessage[], stream: boolean, opts: ChatOpts, extra: boolean) { + const body: Record = { + model: cfg.model, + messages, + stream, + temperature: opts.temperature ?? cfg.temperature, + max_tokens: opts.maxTokens ?? cfg.max_tokens + } + if (extra) { + // 豆包 seed 系列默认可能开启深度思考;关闭以提速并保持输出干净。 + // 不识别该参数的服务商会在 400 中提及,随后自动降级重试。 + body.thinking = { type: 'disabled' } + if (stream) body.stream_options = { include_usage: true } + } + return body +} + +async function doFetch(cfg: LlmConfig, messages: ChatMessage[], stream: boolean, opts: ChatOpts, extra: boolean) { + return fetch(joinUrl(cfg.base_url, '/chat/completions'), { + method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${cfg.api_key}` }, + body: JSON.stringify(buildBody(cfg, messages, stream, opts, extra)), + signal: opts.signal + }) +} + +/** 带降级重试的请求:首次带 thinking disabled / usage 统计,400 时去掉附加参数重发 */ +async function fetchResilient(cfg: LlmConfig, messages: ChatMessage[], stream: boolean, opts: ChatOpts) { + let res = await doFetch(cfg, messages, stream, opts, true) + if (!res.ok) { + const errText = await res.text() + if (res.status === 400 && /thinking|stream_options|unknown|unrecognized/i.test(errText)) { + res = await doFetch(cfg, messages, stream, opts, false) + if (!res.ok) throw new Error(`模型接口 HTTP ${res.status}: ${(await res.text()).slice(0, 500)}`) + } else { + throw new Error(`模型接口 HTTP ${res.status}: ${errText.slice(0, 500)}`) + } + } + return res +} + +/** 流式对话,逐段回调 onDelta,返回完整内容与用量 */ +export async function chatStream(cfg: LlmConfig, messages: ChatMessage[], opts: ChatOpts = {}) { + const res = await fetchResilient(cfg, messages, true, opts) + if (!res.body) throw new Error('模型接口返回空流') + const reader = res.body.getReader() + const decoder = new TextDecoder() + let buf = '' + let content = '' + let usage: Usage | undefined + for (;;) { + const { done, value } = await reader.read() + if (done) break + buf += decoder.decode(value, { stream: true }) + let nl: number + while ((nl = buf.indexOf('\n')) !== -1) { + const line = buf.slice(0, nl).trim() + buf = buf.slice(nl + 1) + if (!line.startsWith('data:')) continue + const payload = line.slice(5).trim() + if (payload === '[DONE]') continue + try { + const json = JSON.parse(payload) + const piece: string = json.choices?.[0]?.delta?.content ?? '' + if (piece) { + content += piece + opts.onDelta?.(piece) + } + if (json.usage) usage = json.usage + const errMsg = json.error?.message + if (errMsg) throw new Error('模型流式返回错误: ' + errMsg) + } catch (e) { + if (e instanceof SyntaxError) continue // 忽略无法解析的行 + throw e + } + } + } + return { content, usage } +} + +/** 非流式对话 */ +export async function chatOnce(cfg: LlmConfig, messages: ChatMessage[], opts: ChatOpts = {}) { + const res = await fetchResilient(cfg, messages, false, opts) + const json = await res.json() + const content: string = json.choices?.[0]?.message?.content ?? '' + if (!content) throw new Error('模型返回内容为空: ' + JSON.stringify(json).slice(0, 300)) + return { content, usage: json.usage as Usage | undefined } +} + +/** 结构化输出:模型 JSON 解析失败时携带错误反馈重试 */ +export async function chatStructured( + cfg: LlmConfig, + messages: ChatMessage[], + validate: (text: string) => T, + opts: ChatOpts = {} +): Promise<{ data: T; raw: string; usage?: Usage }> { + let lastErr: unknown + for (let attempt = 0; attempt < 3; attempt++) { + const msgs = [...messages] + if (attempt > 0 && lastErr instanceof Error) { + msgs.push({ role: 'assistant', content: lastErr.message.slice(0, 100) }) + msgs.push({ role: 'user', content: `上次输出无法解析(${lastErr.message.slice(0, 200)})。请严格只输出符合要求的 JSON,不要任何其他文字。` }) + } + const { content, usage } = await chatStream(cfg, msgs, { + ...opts, + temperature: Math.min(opts.temperature ?? cfg.temperature, 0.7), + maxTokens: Math.min(opts.maxTokens ?? cfg.max_tokens, 16000) + }) + try { + return { data: validate(content), raw: content, usage } + } catch (e) { + lastErr = e + } + } + throw new Error(`模型未能输出合法 JSON(已重试 3 次): ${lastErr instanceof Error ? lastErr.message : lastErr}`) +} + +/** 设置页连通性测试:列模型 + 小对话 */ +export async function testConnection(cfg: Pick) { + const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${cfg.api_key}` } + let models: string[] | undefined + try { + const r = await fetch(joinUrl(cfg.base_url, '/models'), { headers, signal: AbortSignal.timeout(15000) }) + if (r.ok) { + const j = await r.json() + models = (j.data ?? []).map((m: { id: string }) => m.id).slice(0, 50) + } + } catch { + /* models 接口不可用不阻塞测试 */ + } + const r = await fetch(joinUrl(cfg.base_url, '/chat/completions'), { + method: 'POST', + headers, + body: JSON.stringify({ model: cfg.model, messages: [{ role: 'user', content: '请只回复两个字:成功' }], max_tokens: 32, temperature: 0.1 }), + signal: AbortSignal.timeout(60000) + }) + if (!r.ok) throw new Error(`HTTP ${r.status}: ${(await r.text()).slice(0, 400)}`) + const j = await r.json() + const reply: string = j.choices?.[0]?.message?.content ?? '' + return { models, reply: reply.trim().slice(0, 100) } +} diff --git a/server/src/llm/prompts.ts b/server/src/llm/prompts.ts new file mode 100644 index 0000000..cdf1823 --- /dev/null +++ b/server/src/llm/prompts.ts @@ -0,0 +1,362 @@ +import { z } from 'zod' +import type { ChatMessage } from './client.js' + +export interface ProjectLike { + name: string + genre: string + premise: string + style_notes: string + style_sample: string + narrative_pov: string + taboos: string + target_chapter_words: number + target_chapter_count: number +} + +export const characterCardSchema = z.object({ + name: z.string().min(1), + aliases: z.string().default(''), + role: z.string().default('配角'), + appearance: z.string().default(''), + personality: z.string().default(''), + background: z.string().default(''), + goals: z.string().default(''), + speech_pattern: z.string().default(''), + inner_conflict: z.string().default(''), + secret: z.string().default(''), + arc_notes: z.string().default('') +}) +export const characterCardArraySchema = z.array(characterCardSchema).min(1) + +export const worldEntrySchema = z.object({ + category: z.string().default('其他'), + title: z.string().min(1), + content: z.string().default('') +}) +export const worldEntryArraySchema = z.array(worldEntrySchema).min(1) + +export const volumeSchema = z.object({ + title: z.string().min(1), + summary: z.string().default(''), + chapter_count: z.number().int().min(1) +}) +export const volumeArraySchema = z.array(volumeSchema).min(1) + +export const beatSchema = z.object({ + title: z.string().min(1), + events: z.string().default(''), + involved_characters: z.array(z.string()).default([]), + foreshadow_plan: z.string().default(''), + pov: z.string().default(''), + emotional_arc: z.string().default(''), + purpose: z.string().default(''), + tension: z.number().int().min(0).max(5).default(0) +}) +export const beatArraySchema = z.array(beatSchema).min(1) + +export const postprocessSchema = z.object({ + summary: z.string().default(''), + new_world_facts: z.array(worldEntrySchema).default([]), + character_updates: z.array(z.object({ name: z.string(), update: z.string() })).default([]), + new_foreshadows: z.array(z.string()).default([]), + paid_foreshadow_ids: z.array(z.number().int()).default([]), + timeline_events: z + .array(z.object({ story_time: z.string().default(''), title: z.string(), description: z.string().default('') })) + .default([]) +}) + +const JSON_ONLY = '\n只输出 JSON 本身:不要 markdown 代码块、不要解释、不要任何 JSON 以外的文字。' + +function projectBrief(p: ProjectLike): string { + return [ + `【作品名】《${p.name}》`, + `【题材】${p.genre || '未指定'}`, + `【故事创意/梗概】\n${p.premise || '(用户未填写,请依据作品名与题材合理发挥)'}`, + p.narrative_pov ? `【叙事视角】${p.narrative_pov}` : '', + p.taboos ? `【禁用内容】${p.taboos}(严禁出现)` : '', + p.style_notes ? `【文风要求】${p.style_notes}` : '', + `【目标规模】约 ${p.target_chapter_count} 章,每章约 ${p.target_chapter_words} 字` + ] + .filter(Boolean) + .join('\n') +} + +export function charactersMessages(p: ProjectLike): ChatMessage[] { + return [ + { + role: 'system', + content: `你是一位资深中文小说编辑,擅长从故事创意中设计立体的人物。 +请为这部作品设计 4-8 个相互关联、有戏剧张力的人物卡。要求: +- 人物覆盖核心冲突的各方(主角、盟友、对手、反派等),人物之间要有明确的情感或利益关系 +- 性格鲜明独特、有优点也有缺陷,避免脸谱化;配角也要有独立动机 +- goals 区分「表层目标」(人物自己想要的)与深层需求(人物真正缺失的),写入 goals 字段 +- speech_pattern 写语言习惯:口癖、句式偏好、说话音量与节奏、面对冲突时的说话方式(供正文保持声口一致) +- inner_conflict 写内在冲突:欲望与恐惧、理智与情感的拉扯(好人物都由矛盾驱动) +- secret 写秘密:人物知道但不为人知的事,秘密迟早影响剧情(无则空字符串) +- role 只能取:主角、重要配角、配角、反派${JSON_ONLY} +数组元素格式: +{"name":"姓名","aliases":"昵称或别称,可为空字符串","role":"主角","appearance":"外貌与气质","personality":"性格特点","background":"背景经历","goals":"表层目标与深层需求","speech_pattern":"语言习惯与口癖","inner_conflict":"内在冲突","secret":"秘密","arc_notes":"人物弧光与成长线"}` + }, + { role: 'user', content: projectBrief(p) } + ] +} + +export function worldMessages(p: ProjectLike): ChatMessage[] { + return [ + { + role: 'system', + content: `你是一位资深小说世界观架构师。请为这部作品设计世界观设定,共 6-12 条。 +category 只能取:地理、势力、力量体系、物品、规则、历史、其他。 +要求:设定具体可用、能直接支撑剧情写作;条目之间自洽;覆盖故事展开所必需的核心设定${JSON_ONLY} +数组元素格式: +{"category":"地理","title":"条目名","content":"具体设定描述"}` + }, + { role: 'user', content: projectBrief(p) } + ] +} + +export interface CharacterBrief { + name: string + role: string + goals: string + personality: string +} +export interface WorldBrief { + category: string + title: string + content: string +} + +export function volumesMessages(p: ProjectLike, characters: CharacterBrief[], world: WorldBrief[]): ChatMessage[] { + const charText = characters.map((c) => `- ${c.name}(${c.role}):${c.goals || c.personality || ''}`).join('\n') + const worldText = world.map((w) => `- [${w.category}] ${w.title}:${w.content.slice(0, 120)}`).join('\n') + return [ + { + role: 'system', + content: `你是一位资深小说主笔。请为这部作品设计分卷大纲。 +要求: +- 共 1-5 卷(酌情决定),各卷 chapter_count 之和必须恰好等于 ${p.target_chapter_count} +- 整体节奏遵循「开局立意 → 发展升级 → 中段转折 → 高潮决战 → 结局收束」 +- 每卷 summary 说明核心冲突、关键事件与该卷结尾的钩子(150-300 字) +- 充分利用下方人物关系与世界观设定 +- 只输出一套最终分卷方案:每卷只出现一次,严禁输出备选方案、多个版本或重复条目${JSON_ONLY} +数组元素格式: +{"title":"卷名","summary":"本卷大纲","chapter_count":10}` + }, + { + role: 'user', + content: `${projectBrief(p)}\n\n【人物表】\n${charText || '(暂无)'}\n\n【世界观设定】\n${worldText || '(暂无)'}` + } + ] +} + +export function beatsMessages( + p: ProjectLike, + volume: { title: string; summary: string; chapter_count: number }, + characters: CharacterBrief[], + world: WorldBrief[], + prevRecap: string +): ChatMessage[] { + const charText = characters.map((c) => `- ${c.name}(${c.role}):${c.personality};目标:${c.goals}`).join('\n') + const worldText = world.map((w) => `- [${w.category}] ${w.title}:${w.content.slice(0, 150)}`).join('\n') + return [ + { + role: 'system', + content: `你是一位资深小说主笔。请为《${volume.title}》卷设计逐章细纲,共 ${volume.chapter_count} 章。 +每章要求: +- title 形如「第N章 章名」(N 从给定起始编号连续编号) +- events 用 3-6 句话描述本章关键事件,必须包含冲突或转折,结尾留钩子 +- involved_characters 列出本章出场人物姓名(必须从提供的人物表中选取) +- pov 填本章视角人物姓名(从 involved_characters 中选一个,跟随谁的眼睛看故事);用户指定了全书叙事视角时必须遵守 +- emotional_arc 填本章主角情绪曲线,形如「警惕→动摇→决意」(好的章节情绪必须有变化) +- purpose 填本章在全局中的功能,只能取:开局、铺垫、升温、转折、高潮、回落、收束、过渡 +- tension 填本章张力等级 1-5 整数(全书应呈波浪上升,避免连续多章同分) +- foreshadow_plan 描述本章需要埋下或回收的伏笔,可为空字符串 +章节之间承接递进,符合本卷大纲走向;张力和情绪曲线共同构成本卷的节奏 +- 只输出一套最终细纲:每章只出现一次,严禁输出备选方案、多个版本或重复条目${JSON_ONLY} +数组元素格式: +{"title":"第N章 章名","events":"...","involved_characters":["人名"],"pov":"视角人物","emotional_arc":"警惕→动摇→决意","purpose":"升温","tension":3,"foreshadow_plan":"..."}` + }, + { + role: 'user', + content: [ + projectBrief(p), + `【人物表】\n${charText || '(暂无)'}`, + `【世界观设定】\n${worldText || '(暂无)'}`, + prevRecap ? `【前情概要(此前各卷已写内容)】\n${prevRecap}` : '', + `【本卷大纲】${volume.title}:${volume.summary}` + ] + .filter(Boolean) + .join('\n\n') + } + ] +} + +export interface ProseContext { + project: ProjectLike + chapterNo: number + chapterTitle: string + volumeTitle?: string + beat?: { + events: string + foreshadow_plan: string + involved: string[] + pov: string + emotional_arc: string + purpose: string + tension: number + } + characterCards: string[] + worldEntries: string[] + prevSummaries: string[] + prevTail?: string + openForeshadows: string[] +} + +export function proseMessages(ctx: ProseContext): ChatMessage[] { + const styleLine = ctx.project.style_notes ? `\n9. 文风要求:${ctx.project.style_notes}` : '' + const sampleLine = ctx.project.style_sample + ? `\n【文风示例】(模仿其笔法与节奏,严禁抄写其内容):\n${ctx.project.style_sample.slice(0, 800)}` + : '' + return [ + { + role: 'system', + content: `你是一位中文小说写作大师,正在按细纲为长篇小说撰写正文。写作要求: +1. 直接输出正文:第一段就是小说内容,不要输出章节标题、不要任何解释总结、不要出现"本章完"之类字样 +2. 严格依据【本章细纲】推进剧情,细纲中的关键事件必须全部写到,不得擅自偏离 +3. 人物言行必须符合人物卡的性格与语言习惯(speech_pattern);不同人物开口要有可区分的声口 +4. 视角纪律:只写视角人物的所见所闻与内心,其他人物的内心只能通过表情、动作、语气等外部细节侧写,严禁视角漂移 +5. 展示而非陈述(show, don't tell):情绪用动作、生理反应、环境互动来呈现,少用"他很愤怒"式直陈 +6. 对话要有潜台词:人物不会直说真实意图,让立场与情绪藏在措辞、回避和答非所问里 +7. 以场景与对话推动叙事,多用感官细节与人物动作,避免大段静态说明与信息倾泻(背景信息拆碎融进场景) +8. 篇幅约 ${ctx.project.target_chapter_words} 字,自然分段,段落之间空一行;结尾按细纲留钩子${styleLine}${sampleLine}` + }, + { + role: 'user', + content: [ + `【作品】《${ctx.project.name}》 题材:${ctx.project.genre || '未指定'}`, + ctx.project.narrative_pov ? `【全书叙事视角】${ctx.project.narrative_pov}` : '', + ctx.project.taboos ? `【禁用内容(严禁出现)】${ctx.project.taboos}` : '', + ctx.project.premise ? `【故事创意】${ctx.project.premise.slice(0, 600)}` : '', + ctx.worldEntries.length ? `【相关世界观设定】\n${ctx.worldEntries.join('\n')}` : '', + ctx.characterCards.length ? `【人物卡】\n${ctx.characterCards.join('\n\n')}` : '', + ctx.prevSummaries.length ? `【前情提要】\n${ctx.prevSummaries.join('\n')}` : '', + ctx.openForeshadows.length ? `【尚未回收的伏笔】\n${ctx.openForeshadows.map((f, i) => `${i + 1}. ${f}`).join('\n')}` : '', + ctx.prevTail ? `【上一章结尾】\n…${ctx.prevTail}` : '', + `【本章细纲】${ctx.volumeTitle ? `所属卷:${ctx.volumeTitle};` : ''}第${ctx.chapterNo}章《${ctx.chapterTitle}》`, + ctx.beat?.events ? `本章事件:${ctx.beat.events}` : '(无细纲,请依据前情自然推进)', + ctx.beat?.involved.length ? `本章涉及人物:${ctx.beat.involved.join('、')}` : '', + ctx.beat?.pov ? `本章视角人物:${ctx.beat.pov}` : '', + ctx.beat?.emotional_arc ? `本章情绪曲线(起点→终点,必须有变化):${ctx.beat.emotional_arc}` : '', + ctx.beat?.purpose ? `本章功能定位:${ctx.beat.purpose}` : '', + ctx.beat?.tension ? `本章张力等级:${ctx.beat.tension}/5(按等级控制冲突强度与节奏快慢)` : '', + ctx.beat?.foreshadow_plan ? `伏笔安排:${ctx.beat.foreshadow_plan}` : '', + '现在请撰写本章正文。' + ] + .filter(Boolean) + .join('\n\n') + } + ] +} + +export interface RewriteParams { + mode: 'rewrite' | 'expand' | 'shrink' | 'custom' + instruction?: string + before: string + selected: string + after: string +} + +export function rewriteMessages(p: RewriteParams): ChatMessage[] { + const modeText = { + rewrite: '重写该段:保持情节信息不变,提升文笔、画面感与人物声口', + expand: '扩写该段:补充动作、细节与心理描写,篇幅约为原来的 1.5-2 倍', + shrink: '压缩该段:保留关键信息,篇幅约为原来的一半', + custom: `按用户指令改写该段:${p.instruction || '(未给出指令)'}` + }[p.mode] + return [ + { + role: 'system', + content: `你是中文小说润色师。任务:${modeText}。 +要求:与前后文自然衔接;保持叙事视角与人称一致;只输出改写后的段落文本本身,不要解释、不要用引号包裹。` + }, + { + role: 'user', + content: `${p.before ? `【前文】\n…${p.before}\n\n` : ''}【选中段落】\n${p.selected}\n\n${p.after ? `【后文】\n${p.after}…\n\n` : ''}请输出改写后的段落。` + } + ] +} + +export interface CharacterBackfillInput { + name: string + role: string + aliases: string + personality: string + background: string + goals: string +} + +export const characterBackfillSchema = z.object({ + name: z.string().min(1), + speech_pattern: z.string().default(''), + inner_conflict: z.string().default(''), + secret: z.string().default('') +}) +export const characterBackfillArraySchema = z.array(characterBackfillSchema).min(1) + +/** 为已有人物卡补写语言习惯 / 内在冲突 / 秘密三项专业字段 */ +export function characterBackfillMessages(chars: CharacterBackfillInput[], p: ProjectLike): ChatMessage[] { + const list = chars + .map( + (c) => + `- ${c.name}(${c.role}${c.aliases ? `,别称${c.aliases}` : ''}):性格 ${c.personality.slice(0, 120) || '(未填)'};背景 ${c.background.slice(0, 120) || '(未填)'};目标 ${c.goals.slice(0, 100) || '(未填)'}` + ) + .join('\n') + return [ + { + role: 'system', + content: `你是资深小说编辑。请依据给出的人物卡要点,为每个人物补写三项专业字段: +- speech_pattern 语言习惯与口癖:口头禅、句式偏好、说话节奏与音量、面对冲突时的说话方式;不同人物的声口必须彼此可区分 +- inner_conflict 内在冲突:欲望与恐惧、理智与情感的拉扯,一句话点到要害 +- secret 秘密:该人物知道但不为人知、未来可能影响剧情的事;没有秘密可填空字符串,但至少一半人物要有实质秘密 +只输出 JSON 数组:每个人物一条,name 必须与输入完全一致,不得增删或改写人物${JSON_ONLY} +数组元素格式: +{"name":"姓名","speech_pattern":"...","inner_conflict":"...","secret":"..."}` + }, + { role: 'user', content: `【作品】《${p.name}》(${p.genre || '未指定题材'})\n【人物】\n${list}` } + ] +} + +export interface PostprocessInput { + chapterTitle: string + chapterNo: number + content: string + foreshadows: { id: number; description: string }[] + characterNames: string[] +} + +export function postprocessMessages(input: PostprocessInput): ChatMessage[] { + return [ + { + role: 'system', + content: `你是小说连载维护助手。阅读给出的章节正文,输出结构化 JSON 用于维护"故事圣经"。 +- summary:本章剧情摘要,120-200 字,第三人称 +- new_world_facts:本章新出现且值得长期记住的设定(新的地点、规则、物品、组织等),没有则空数组 +- character_updates:本章中人物的状态/关系/能力/立场发生的变化,name 必须是已有人物名 +- new_foreshadows:本章埋下的新伏笔(一句话一条) +- paid_foreshadow_ids:从提供的未回收伏笔列表中,选出本章已经回收/兑现的伏笔 id +- timeline_events:本章发生的关键事件(按故事内时间排序),story_time 填故事内时间(可为空字符串)${JSON_ONLY} +格式: +{"summary":"...","new_world_facts":[{"category":"地理|势力|力量体系|物品|规则|历史|其他","title":"...","content":"..."}],"character_updates":[{"name":"...","update":"..."}],"new_foreshadows":["..."],"paid_foreshadow_ids":[1,2],"timeline_events":[{"story_time":"...","title":"...","description":"..."}]}` + }, + { + role: 'user', + content: [ + `【章节】第${input.chapterNo}章《${input.chapterTitle}》`, + `【已有人物】${input.characterNames.join('、') || '(无)'}`, + `【未回收伏笔】${input.foreshadows.length ? JSON.stringify(input.foreshadows) : '(无)'}`, + `【正文】\n${input.content}` + ].join('\n') + } + ] +} diff --git a/server/src/pipeline/context.ts b/server/src/pipeline/context.ts new file mode 100644 index 0000000..b14b971 --- /dev/null +++ b/server/src/pipeline/context.ts @@ -0,0 +1,108 @@ +import { db } from '../db/connection.js' +import type { BeatParsed, ChapterRow, CharacterRow, ForeshadowRow, ProjectRow, TimelineEventRow, VolumeRow, WorldEntryRow } from '../db/types.js' +import { parseInvolved } from '../db/types.js' +import type { ProseContext } from '../llm/prompts.js' +import type { ProjectLike } from '../llm/prompts.js' + +export interface Bundle { + project: ProjectRow + characters: CharacterRow[] + world: WorldEntryRow[] + volumes: VolumeRow[] + beats: BeatParsed[] + chapters: ChapterRow[] + foreshadows: ForeshadowRow[] + timeline: TimelineEventRow[] +} + +export function loadBundle(projectId: number): Bundle | null { + const project = db.prepare('SELECT * FROM projects WHERE id = ?').get(projectId) as ProjectRow | undefined + if (!project) return null + // involved_characters 以 JSON 字符串存储,出站统一解析为数组 + const beats = (db.prepare('SELECT * FROM beats WHERE project_id = ? ORDER BY sort, id').all(projectId) as unknown as BeatParsed[]).map( + (b) => ({ ...b, involved_characters: parseInvolved(b.involved_characters as unknown as string) }) + ) + return { + project, + characters: db.prepare('SELECT * FROM characters WHERE project_id = ? ORDER BY sort, id').all(projectId) as unknown as CharacterRow[], + world: db.prepare('SELECT * FROM world_entries WHERE project_id = ? ORDER BY sort, id').all(projectId) as unknown as WorldEntryRow[], + volumes: db.prepare('SELECT * FROM volumes WHERE project_id = ? ORDER BY sort, id').all(projectId) as unknown as VolumeRow[], + beats, + chapters: db.prepare('SELECT * FROM chapters WHERE project_id = ? ORDER BY sort, id').all(projectId) as unknown as ChapterRow[], + foreshadows: db.prepare('SELECT * FROM foreshadows WHERE project_id = ? ORDER BY id').all(projectId) as unknown as ForeshadowRow[], + timeline: db.prepare('SELECT * FROM timeline_events WHERE project_id = ? ORDER BY sort, id').all(projectId) as unknown as TimelineEventRow[] + } +} + +export function toProjectLike(p: ProjectRow): ProjectLike { + return { + name: p.name, + genre: p.genre, + premise: p.premise, + style_notes: p.style_notes, + style_sample: p.style_sample, + narrative_pov: p.narrative_pov, + taboos: p.taboos, + target_chapter_words: p.target_chapter_words, + target_chapter_count: p.target_chapter_count + } +} + +/** 为指定章节组装正文生成上下文 */ +export function buildProseContext(bundle: Bundle, chapter: ChapterRow): ProseContext { + const chapterNo = bundle.chapters.findIndex((c) => c.id === chapter.id) + 1 + const volume = bundle.volumes.find((v) => v.id === chapter.volume_id) + const beat = bundle.beats.find((b) => b.id === chapter.beat_id) + + // 人物卡:细纲指定了涉及人物则取这些,否则取全部(上限 12 张) + const involvedIds = beat?.involved_characters ?? [] + let chars = involvedIds.length + ? involvedIds.map((id) => bundle.characters.find((c) => c.id === id)).filter((c): c is CharacterRow => !!c) + : bundle.characters.slice(0, 12) + if (chars.length === 0) chars = bundle.characters.slice(0, 12) + const characterCards = chars.map((c) => + `【${c.name}】${c.aliases ? `(别称:${c.aliases})` : ''}(${c.role})\n外貌:${c.appearance}\n性格:${c.personality}\n背景:${c.background}\n目标:${c.goals}${c.speech_pattern ? `\n语言习惯:${c.speech_pattern}` : ''}${c.inner_conflict ? `\n内在冲突:${c.inner_conflict}` : ''}${c.secret ? `\n秘密(绝不可被其他人物直接知晓,除非剧情已揭示):${c.secret}` : ''}${c.arc_notes ? `\n近况与弧光:${c.arc_notes.slice(-400)}` : ''}` + ) + + // 世界观:数量少则全量注入,多则注入与细纲文本相关的条目 + const beatText = beat ? `${beat.title} ${beat.events} ${beat.foreshadow_plan}` : chapter.title + const worldEntries = ( + bundle.world.length <= 15 + ? bundle.world + : bundle.world.filter((w) => beatText.includes(w.title) || w.category === '规则' || w.category === '力量体系') + ) + .slice(0, 15) + .map((w) => `[${w.category}] ${w.title}:${w.content}`) + + // 前情:所有已写章节的摘要 + 上一章结尾 500 字 + const idx = bundle.chapters.findIndex((c) => c.id === chapter.id) + const prevChapters = bundle.chapters.slice(0, Math.max(idx, 0)).filter((c) => c.content.trim().length > 0) + const prevSummaries = prevChapters.map((c, i) => `第${i + 1}章《${c.title}》:${c.summary || c.content.slice(0, 120) + '…'}`) + const prev = idx > 0 ? bundle.chapters[idx - 1] : undefined + const prevTail = prev && prev.content.trim() ? prev.content.replace(/\s+/g, ' ').slice(-500) : undefined + + const openForeshadows = bundle.foreshadows.filter((f) => f.status === 'planted').map((f) => f.description) + + return { + project: toProjectLike(bundle.project), + chapterNo: chapterNo || 1, + chapterTitle: chapter.title, + volumeTitle: volume?.title, + beat: beat + ? { + events: beat.events, + foreshadow_plan: beat.foreshadow_plan, + involved: beat.involved_characters.map((id) => bundle.characters.find((c) => c.id === id)?.name ?? '').filter(Boolean), + pov: beat.pov, + emotional_arc: beat.emotional_arc, + purpose: beat.purpose, + tension: beat.tension + } + : undefined, + characterCards, + worldEntries, + prevSummaries, + prevTail, + openForeshadows + } +} diff --git a/server/src/pipeline/postprocess.ts b/server/src/pipeline/postprocess.ts new file mode 100644 index 0000000..2ab878a --- /dev/null +++ b/server/src/pipeline/postprocess.ts @@ -0,0 +1,117 @@ +import { db, now } from '../db/connection.js' +import type { ChapterRow, CharacterRow, ForeshadowRow } from '../db/types.js' +import { getLlmConfig } from '../db/seed.js' +import { chatStructured } from '../llm/client.js' +import { postprocessMessages, postprocessSchema } from '../llm/prompts.js' +import { parseStructured } from '../util/json.js' +import { loadBundle } from './context.js' +import { countWords } from '../util/words.js' + +export interface PostprocessResult { + summary: string + new_world_facts: number + character_updates: number + new_foreshadows: number + paid_foreshadows: number + timeline_events: number +} + +/** 章节正文生成完成后:提取摘要/新设定/人物变化/伏笔/时间线并写回故事圣经 */ +export async function runPostprocess(projectId: number, chapterId: number): Promise { + const bundle = loadBundle(projectId) + if (!bundle) throw new Error('项目不存在') + const chapter = bundle.chapters.find((c) => c.id === chapterId) as ChapterRow | undefined + if (!chapter) throw new Error('章节不存在') + const content = chapter.content.trim() + if (!content) throw new Error('章节内容为空,无法回写') + + const chapterNo = bundle.chapters.findIndex((c) => c.id === chapterId) + 1 + const open = bundle.foreshadows.filter((f: ForeshadowRow) => f.status === 'planted') + const cfg = getLlmConfig() + + const { data, usage } = await chatStructured( + cfg, + postprocessMessages({ + chapterTitle: chapter.title, + chapterNo: chapterNo || 1, + content, + foreshadows: open.map((f) => ({ id: f.id, description: f.description.slice(0, 80) })), + characterNames: bundle.characters.map((c: CharacterRow) => c.name) + }), + (text) => parseStructured(text, postprocessSchema), + { temperature: 0.3 } + ) + + db.prepare( + 'INSERT INTO generation_logs (project_id, stage, model, prompt_tokens, completion_tokens, ok, created_at) VALUES (?,?,?,?,?,1,?)' + ).run(projectId, 'postprocess', cfg.model, usage?.prompt_tokens ?? 0, usage?.completion_tokens ?? 0, now()) + + db.exec('BEGIN') + try { + // 1. 摘要与字数 + db.prepare('UPDATE chapters SET summary = ?, word_count = ?, updated_at = ? WHERE id = ?').run( + data.summary, + countWords(content), + now(), + chapterId + ) + // 2. 新世界观条目 + for (const w of data.new_world_facts) { + db.prepare('INSERT INTO world_entries (project_id, category, title, content, sort, updated_at) VALUES (?,?,?,?,?,?)').run( + projectId, + w.category || '其他', + w.title, + w.content, + 999, + now() + ) + } + // 3. 人物近况追加到 arc_notes + for (const u of data.character_updates) { + const char = bundle.characters.find((c) => c.name === u.name || (c.aliases && c.aliases.includes(u.name))) + if (!char || !u.update) continue + const appended = `${char.arc_notes ? char.arc_notes + '\n' : ''}[第${chapterNo}章] ${u.update}` + db.prepare('UPDATE characters SET arc_notes = ?, updated_at = ? WHERE id = ?').run(appended.slice(-2000), now(), char.id) + } + // 4. 新伏笔 + for (const desc of data.new_foreshadows) { + if (!desc.trim()) continue + db.prepare( + 'INSERT INTO foreshadows (project_id, description, planted_chapter_id, status, notes, updated_at) VALUES (?,?,?,?,?,?)' + ).run(projectId, desc.trim(), chapterId, 'planted', '', now()) + } + // 5. 回收伏笔 + for (const id of data.paid_foreshadow_ids) { + const f = open.find((x) => x.id === id) + if (!f) continue + db.prepare("UPDATE foreshadows SET status = 'paid', paid_chapter_id = ?, updated_at = ? WHERE id = ?").run(chapterId, now(), id) + } + // 6. 时间线 + let sortBase = (db.prepare('SELECT COALESCE(MAX(sort),0) AS m FROM timeline_events WHERE project_id = ?').get(projectId) as { m: number }).m + for (const t of data.timeline_events) { + if (!t.title.trim()) continue + sortBase += 1 + db.prepare('INSERT INTO timeline_events (project_id, chapter_id, story_time, title, description, sort) VALUES (?,?,?,?,?,?)').run( + projectId, + chapterId, + t.story_time, + t.title, + t.description, + sortBase + ) + } + db.exec('COMMIT') + } catch (e) { + db.exec('ROLLBACK') + throw e + } + + return { + summary: data.summary, + new_world_facts: data.new_world_facts.length, + character_updates: data.character_updates.length, + new_foreshadows: data.new_foreshadows.length, + paid_foreshadows: data.paid_foreshadow_ids.length, + timeline_events: data.timeline_events.length + } +} diff --git a/server/src/routes/assets.ts b/server/src/routes/assets.ts new file mode 100644 index 0000000..0fed52c --- /dev/null +++ b/server/src/routes/assets.ts @@ -0,0 +1,137 @@ +import { Router } from 'express' +import { z } from 'zod' +import type { SQLInputValue } from 'node:sqlite' +import { db, now } from '../db/connection.js' + +/** 人物 / 世界观 / 伏笔 / 时间线 的 CRUD */ + +interface TableSpec { + path: string + table: string + orderBy: string + schema: z.ZodObject + touchUpdatedAt: boolean +} + +function crudRouter(spec: TableSpec): Router { + const r = Router() + const { table, schema } = spec + + r.get(`/projects/:projectId/${spec.path}`, (req, res) => { + res.json(db.prepare(`SELECT * FROM ${table} WHERE project_id = ? ORDER BY ${spec.orderBy}`).all(Number(req.params.projectId))) + }) + + r.post(`/projects/:projectId/${spec.path}`, (req, res) => { + const projectId = Number(req.params.projectId) + if (!db.prepare('SELECT id FROM projects WHERE id = ?').get(projectId)) return res.status(404).json({ error: '项目不存在' }) + const parsed = schema.safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: parsed.error.issues[0].message }) + const d = parsed.data as Record + // zod 已为所有字段补默认值,全部入库 + const cols = ['project_id', ...Object.keys(schema.shape)] + const values: SQLInputValue[] = [projectId, ...cols.slice(1).map((c) => d[c] as SQLInputValue)] + if (spec.touchUpdatedAt) { + cols.push('updated_at') + values.push(now()) + } + const info = db + .prepare(`INSERT INTO ${table} (${cols.join(',')}) VALUES (${cols.map(() => '?').join(',')})`) + .run(...values) + res.json(db.prepare(`SELECT * FROM ${table} WHERE id = ?`).get(info.lastInsertRowid)) + }) + + // 注意:按前端调用的 path(如 world / timeline)注册,而不是数据库表名(world_entries / timeline_events) + r.patch(`/${spec.path}/:id`, (req, res) => { + const parsed = schema.partial().safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: parsed.error.issues[0].message }) + const entries = Object.entries(parsed.data).filter(([, v]) => v !== undefined) as [string, SQLInputValue][] + if (entries.length === 0) return res.status(400).json({ error: '无更新字段' }) + if (spec.touchUpdatedAt) entries.push(['updated_at', now()]) + const setClause = entries.map(([c]) => `${c} = ?`).join(', ') + db.prepare(`UPDATE ${table} SET ${setClause} WHERE id = ?`).run(...entries.map(([, v]) => v), Number(req.params.id)) + res.json(db.prepare(`SELECT * FROM ${table} WHERE id = ?`).get(Number(req.params.id))) + }) + + r.delete(`/${spec.path}/:id`, (req, res) => { + db.prepare(`DELETE FROM ${table} WHERE id = ?`).run(Number(req.params.id)) + res.json({ ok: true }) + }) + + return r +} + +export const assetsRouter = Router() + +assetsRouter.use( + crudRouter({ + path: 'characters', + table: 'characters', + orderBy: 'sort, id', + + touchUpdatedAt: true, + schema: z.object({ + name: z.string().min(1).max(60), + aliases: z.string().max(500).default(''), + role: z.string().max(30).default('配角'), + appearance: z.string().max(6000).default(''), + personality: z.string().max(6000).default(''), + background: z.string().max(12000).default(''), + goals: z.string().max(6000).default(''), + speech_pattern: z.string().max(6000).default(''), + inner_conflict: z.string().max(6000).default(''), + secret: z.string().max(6000).default(''), + arc_notes: z.string().max(12000).default(''), + sort: z.number().int().default(0) + }) + }) +) + +assetsRouter.use( + crudRouter({ + path: 'world', + table: 'world_entries', + orderBy: 'sort, id', + + touchUpdatedAt: true, + schema: z.object({ + category: z.string().max(30).default('其他'), + title: z.string().min(1).max(100), + content: z.string().max(12000).default(''), + sort: z.number().int().default(0) + }) + }) +) + +assetsRouter.use( + crudRouter({ + path: 'foreshadows', + table: 'foreshadows', + orderBy: 'id', + + touchUpdatedAt: true, + schema: z.object({ + description: z.string().min(1).max(4000), + planted_chapter_id: z.number().int().nullable().default(null), + paid_chapter_id: z.number().int().nullable().default(null), + status: z.enum(['planted', 'paid']).default('planted'), + notes: z.string().max(8000).default('') + }) + }) +) + +assetsRouter.use( + crudRouter({ + path: 'timeline', + table: 'timeline_events', + orderBy: 'sort, id', + + touchUpdatedAt: false, + schema: z.object({ + chapter_id: z.number().int().nullable().default(null), + story_time: z.string().max(100).default(''), + title: z.string().min(1).max(200), + description: z.string().max(8000).default(''), + sort: z.number().int().default(0) + }) + }) +) diff --git a/server/src/routes/chapters.ts b/server/src/routes/chapters.ts new file mode 100644 index 0000000..e0b8d07 --- /dev/null +++ b/server/src/routes/chapters.ts @@ -0,0 +1,82 @@ +import { Router } from 'express' +import { z } from 'zod' +import { db, now } from '../db/connection.js' +import { countWords } from '../util/words.js' +import { runPostprocess } from '../pipeline/postprocess.js' + +export const chaptersRouter = Router() + +const chapterInput = z.object({ + beat_id: z.number().int().nullable().default(null), + volume_id: z.number().int().nullable().default(null), + title: z.string().min(1).max(120), + sort: z.number().int().default(0) +}) + +chaptersRouter.post('/projects/:projectId/chapters', (req, res) => { + const projectId = Number(req.params.projectId) + const parsed = chapterInput.safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: parsed.error.issues[0].message }) + const d = parsed.data + const info = db + .prepare('INSERT INTO chapters (project_id, beat_id, volume_id, sort, title, updated_at) VALUES (?,?,?,?,?,?)') + .run(projectId, d.beat_id, d.volume_id, d.sort, d.title, now()) + res.json(db.prepare('SELECT * FROM chapters WHERE id = ?').get(info.lastInsertRowid)) +}) + +chaptersRouter.get('/chapters/:id', (req, res) => { + const row = db.prepare('SELECT * FROM chapters WHERE id = ?').get(Number(req.params.id)) + if (!row) return res.status(404).json({ error: '章节不存在' }) + res.json(row) +}) + +chaptersRouter.patch('/chapters/:id', (req, res) => { + const parsed = z + .object({ + title: z.string().min(1).max(120).optional(), + content: z.string().max(500000).optional(), + status: z.enum(['empty', 'generated', 'edited']).optional(), + sort: z.number().int().optional(), + volume_id: z.number().int().nullable().optional(), + summary: z.string().max(4000).optional() + }) + .safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: parsed.error.issues[0].message }) + const d = parsed.data + const cur = db.prepare('SELECT * FROM chapters WHERE id = ?').get(Number(req.params.id)) as + | { content: string; word_count: number } + | undefined + if (!cur) return res.status(404).json({ error: '章节不存在' }) + + const wordCount = d.content !== undefined ? countWords(d.content) : cur.word_count + db.prepare('UPDATE chapters SET title = COALESCE(?, title), content = COALESCE(?, content), status = COALESCE(?, status), sort = COALESCE(?, sort), volume_id = COALESCE(?, volume_id), summary = COALESCE(?, summary), word_count = ?, updated_at = ? WHERE id = ?').run( + d.title ?? null, + d.content ?? null, + d.status ?? null, + d.sort ?? null, + d.volume_id ?? null, + d.summary ?? null, + wordCount, + now(), + Number(req.params.id) + ) + res.json(db.prepare('SELECT * FROM chapters WHERE id = ?').get(Number(req.params.id))) +}) + +chaptersRouter.delete('/chapters/:id', (req, res) => { + db.prepare('DELETE FROM chapters WHERE id = ?').run(Number(req.params.id)) + res.json({ ok: true }) +}) + +// 正文定稿后触发:AI 提取摘要/设定/人物变化/伏笔/时间线并写回故事圣经 +chaptersRouter.post('/chapters/:id/postprocess', async (req, res) => { + const id = Number(req.params.id) + const row = db.prepare('SELECT project_id FROM chapters WHERE id = ?').get(id) as { project_id: number } | undefined + if (!row) return res.status(404).json({ error: '章节不存在' }) + try { + const result = await runPostprocess(row.project_id, id) + res.json(result) + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }) + } +}) diff --git a/server/src/routes/export.ts b/server/src/routes/export.ts new file mode 100644 index 0000000..2fac1d0 --- /dev/null +++ b/server/src/routes/export.ts @@ -0,0 +1,52 @@ +import { Router } from 'express' +import { loadBundle } from '../pipeline/context.js' +import { buildEpub } from '../util/epub.js' +import type { EpubChapter } from '../util/epub.js' + +export const exportRouter = Router() + +function collect(projectId: number) { + const bundle = loadBundle(projectId) + if (!bundle) return null + const written = bundle.chapters.filter((c) => c.content.trim().length > 0) + return { bundle, written } +} + +const filenameHeader = (name: string, ext: string) => + `attachment; filename*=UTF-8''${encodeURIComponent(`${name}.${ext}`)}` + +exportRouter.get('/export/:projectId/txt', (req, res) => { + const data = collect(Number(req.params.projectId)) + if (!data) return res.status(404).json({ error: '项目不存在' }) + const { bundle, written } = data + const parts: string[] = [`《${bundle.project.name}》`, bundle.project.genre ? `题材:${bundle.project.genre}` : '', ''] + for (const c of written) { + const vol = bundle.volumes.find((v) => v.id === c.volume_id) + parts.push(`\n\n══════ 第${bundle.chapters.findIndex((x) => x.id === c.id) + 1}章 ${c.title} ══════\n`) + if (vol) parts.push(`(${vol.title})\n\n`) + parts.push(c.content.trim()) + } + res.set('Content-Type', 'text/plain; charset=utf-8') + res.set('Content-Disposition', filenameHeader(bundle.project.name, 'txt')) + res.send('\ufeff' + parts.join('\n')) +}) + +exportRouter.get('/export/:projectId/epub', async (req, res) => { + const data = collect(Number(req.params.projectId)) + if (!data) return res.status(404).json({ error: '项目不存在' }) + const { bundle, written } = data + if (written.length === 0) return res.status(400).json({ error: '暂无已写章节,无法导出' }) + const chapters: EpubChapter[] = written.map((c) => ({ + volumeTitle: bundle.volumes.find((v) => v.id === c.volume_id)?.title, + title: c.title, + paragraphs: c.content.split(/\n+/).map((s) => s.trim()).filter(Boolean) + })) + try { + const buf = await buildEpub(bundle.project.name, chapters) + res.set('Content-Type', 'application/epub+zip') + res.set('Content-Disposition', filenameHeader(bundle.project.name, 'epub')) + res.send(buf) + } catch (e) { + res.status(500).json({ error: e instanceof Error ? e.message : String(e) }) + } +}) diff --git a/server/src/routes/generate.ts b/server/src/routes/generate.ts new file mode 100644 index 0000000..5692537 --- /dev/null +++ b/server/src/routes/generate.ts @@ -0,0 +1,450 @@ +import { Router } from 'express' +import type { Request, Response } from 'express' +import type { SQLInputValue } from 'node:sqlite' +import { z } from 'zod' +import { db, now } from '../db/connection.js' +import { getLlmConfig } from '../db/seed.js' +import type { BeatParsed, BeatRow, ChapterRow, VolumeRow } from '../db/types.js' +import { parseInvolved } from '../db/types.js' +import { chatStream, chatStructured, type ChatMessage } from '../llm/client.js' +import { + beatArraySchema, + beatsMessages, + characterBackfillArraySchema, + characterBackfillMessages, + characterCardArraySchema, + charactersMessages, + proseMessages, + rewriteMessages, + volumeArraySchema, + volumesMessages, + worldEntryArraySchema, + worldMessages +} from '../llm/prompts.js' +import { buildProseContext, loadBundle, toProjectLike } from '../pipeline/context.js' +import { parseStructured } from '../util/json.js' +import { countWords } from '../util/words.js' +import { sseInit, sseSend } from '../util/sse.js' +import { mapNamesToIds } from './outline.js' + +export const generateRouter = Router() + +/** 通用流式执行:把模型增量透传为 SSE,结束时回调并记录日志 */ +async function streamStage(req: Request, res: Response, stage: string, projectId: number, messages: Parameters[1]) { + sseInit(res) + const cfg = getLlmConfig() + const abort = new AbortController() + req.on('close', () => abort.abort()) + let usage: { prompt_tokens?: number; completion_tokens?: number } | undefined + try { + const result = await chatStream(cfg, messages, { + signal: abort.signal, + onDelta: (t) => sseSend(res, 'delta', { text: t }) + }) + usage = result.usage + sseSend(res, 'done', { content: result.content, usage: result.usage }) + db.prepare('INSERT INTO generation_logs (project_id, stage, model, prompt_tokens, completion_tokens, ok, created_at) VALUES (?,?,?,?,?,1,?)').run( + projectId, + stage, + cfg.model, + result.usage?.prompt_tokens ?? 0, + result.usage?.completion_tokens ?? 0, + now() + ) + } catch (e) { + const msg = e instanceof Error ? e.message : String(e) + if (!abort.signal.aborted) { + sseSend(res, 'error', { message: msg }) + db.prepare('INSERT INTO generation_logs (project_id, stage, model, ok, created_at) VALUES (?,?,?,0,?)').run(projectId, stage, cfg.model, now()) + } + } finally { + res.end() + } +} + +/** 结构化生成阶段:流式输出原始文本 + done 事件携带解析后的 JSON */ +async function structuredStage( + req: Request, + res: Response, + stage: string, + projectId: number, + messages: Parameters[1], + validate: (text: string) => T +) { + sseInit(res) + const cfg = getLlmConfig() + const abort = new AbortController() + req.on('close', () => abort.abort()) + try { + const { data, usage } = await chatStructured( + cfg, + messages, + validate, + { signal: abort.signal, onDelta: (t) => sseSend(res, 'delta', { text: t }) } + ) + sseSend(res, 'done', { data, usage }) + db.prepare('INSERT INTO generation_logs (project_id, stage, model, prompt_tokens, completion_tokens, ok, created_at) VALUES (?,?,?,?,?,1,?)').run( + projectId, + stage, + cfg.model, + usage?.prompt_tokens ?? 0, + usage?.completion_tokens ?? 0, + now() + ) + } catch (e) { + const msg = e instanceof Error ? e.message : String(e) + if (!abort.signal.aborted) { + sseSend(res, 'error', { message: msg }) + db.prepare('INSERT INTO generation_logs (project_id, stage, model, ok, created_at) VALUES (?,?,?,0,?)').run(projectId, stage, cfg.model, now()) + } + } finally { + res.end() + } +} + +const pidBody = z.object({ projectId: z.number().int(), requirement: z.string().max(3000).optional() }) + +/** 把用户对本步骤的额外要求追加到最后一条 user 消息(人工约束优先于默认策略) */ +function withRequirement(messages: ChatMessage[], requirement?: string): ChatMessage[] { + const text = requirement?.trim() + if (!text) return messages + const msgs = [...messages] + const last = msgs[msgs.length - 1] + msgs[msgs.length - 1] = { ...last, content: `${last.content}\n\n【用户对本次生成的额外要求(必须遵守,与默认要求冲突时以用户要求为准)】\n${text}` } + return msgs +} + +/** 提取标题中的序号(卷一/第1卷/第一部/第12章),模型输出多版本方案时据此去重 */ +function titleOrdinal(title: string, kind: 'vol' | 'beat'): string | null { + const t = title.replace(/\s+/g, '') + if (kind === 'beat') return /^第([0-90-9一二三四五六七八九十百]+)[章回]/.exec(t)?.[1] ?? null + // 「第1卷 / 1卷 / 第一部」数字在前,「卷一 / 部二」数字在后,两种写法都支持 + const num = '[0-90-9一二三四五六七八九十百]+' + const m = new RegExp(`^(?:第)?(${num})[卷部篇]`).exec(t) ?? new RegExp(`^[卷部篇](${num})`).exec(t) + return m?.[1] ?? null +} + +function normalizeTitleKey(title: string): string { + return title.replace(/[\s::||·、,,。.\-—~~()()【】[\]]+/g, '') +} + +/** 模型偶尔会输出多套方案(同一序号出现多次),只保留每个序号的第一条 */ +function dedupeByTitle(items: T[], titleOf: (t: T) => string, kind: 'vol' | 'beat'): T[] { + const seenOrd = new Set() + const seenText = new Set() + const out: T[] = [] + for (const it of items) { + const ord = titleOrdinal(titleOf(it), kind) + if (ord) { + if (seenOrd.has(ord)) continue + seenOrd.add(ord) + } else { + const key = normalizeTitleKey(titleOf(it)) + if (seenText.has(key)) continue + seenText.add(key) + } + out.push(it) + } + return out +} + +// ---------- 人物卡 ---------- +generateRouter.post('/generate/characters', (req, res) => { + const parsed = pidBody.safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: '参数错误' }) + const bundle = loadBundle(parsed.data.projectId) + if (!bundle) return res.status(404).json({ error: '项目不存在' }) + void structuredStage(req, res, 'characters', bundle.project.id, withRequirement(charactersMessages(toProjectLike(bundle.project)), parsed.data.requirement), (t) => + parseStructured(t, characterCardArraySchema) + ) +}) + +// ---------- 世界观 ---------- +generateRouter.post('/generate/world', (req, res) => { + const parsed = pidBody.safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: '参数错误' }) + const bundle = loadBundle(parsed.data.projectId) + if (!bundle) return res.status(404).json({ error: '项目不存在' }) + void structuredStage(req, res, 'world', bundle.project.id, withRequirement(worldMessages(toProjectLike(bundle.project)), parsed.data.requirement), (t) => + parseStructured(t, worldEntryArraySchema) + ) +}) + +// ---------- 分卷大纲 ---------- +generateRouter.post('/generate/volumes', (req, res) => { + const parsed = pidBody.safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: '参数错误' }) + const bundle = loadBundle(parsed.data.projectId) + if (!bundle) return res.status(404).json({ error: '项目不存在' }) + void structuredStage(req, res, 'volumes', bundle.project.id, withRequirement(volumesMessages(toProjectLike(bundle.project), bundle.characters, bundle.world), parsed.data.requirement), (t) => + dedupeByTitle(parseStructured(t, volumeArraySchema), (v) => v.title, 'vol') + ) +}) + +// ---------- 分章细纲(按卷) ---------- +generateRouter.post('/generate/beats', (req, res) => { + const parsed = z.object({ projectId: z.number().int(), volumeId: z.number().int(), requirement: z.string().max(3000).optional() }).safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: '参数错误' }) + const bundle = loadBundle(parsed.data.projectId) + if (!bundle) return res.status(404).json({ error: '项目不存在' }) + const volume = bundle.volumes.find((v: VolumeRow) => v.id === parsed.data.volumeId) + if (!volume) return res.status(404).json({ error: '卷不存在' }) + + // 起始编号 = 此前所有卷的细纲章数之和 + 1;前情概要 = 此前各卷细纲标题序列 + const sortedVolumes = [...bundle.volumes].sort((a, b) => a.sort - b.sort) + const volumeIdx = sortedVolumes.findIndex((v) => v.id === volume.id) + const prevVolumes = sortedVolumes.slice(0, volumeIdx) + const startNo = prevVolumes.reduce((acc, v) => acc + bundle.beats.filter((b: BeatParsed) => b.volume_id === v.id).length, 0) + 1 + const prevRecap = prevVolumes + .map((v) => { + const volBeats = bundle.beats.filter((b: BeatParsed) => b.volume_id === v.id).sort((a, b) => a.sort - b.sort) + return `《${v.title}》:${volBeats.map((b) => b.title.replace(/^第\d+章\s*/, '')).join('、')}` + }) + .join('\n') + + // 本卷目标章数:应用大纲时创建的空章节数;没有章节信息时默认 10 + const existing = bundle.chapters.filter((c: ChapterRow) => c.volume_id === volume.id).length + const chapterCount = existing > 0 ? existing : 10 + const messages = beatsMessages( + toProjectLike(bundle.project), + { title: volume.title, summary: volume.summary, chapter_count: chapterCount }, + bundle.characters, + bundle.world, + prevRecap + ) + messages[1] = { + role: 'user', + content: `${messages[1].content}\n\n【编号要求】章节从第${startNo}章开始连续编号;共 ${chapterCount} 章,不可多不可少。` + } + void structuredStage(req, res, 'beats', bundle.project.id, withRequirement(messages, parsed.data.requirement), (t) => + dedupeByTitle(parseStructured(t, beatArraySchema), (b) => b.title, 'beat') + ) +}) + +// ---------- 正文(流式) ---------- +generateRouter.post('/generate/prose', (req, res) => { + const parsed = z.object({ projectId: z.number().int(), chapterId: z.number().int(), requirement: z.string().max(3000).optional() }).safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: '参数错误' }) + const bundle = loadBundle(parsed.data.projectId) + if (!bundle) return res.status(404).json({ error: '项目不存在' }) + const chapter = bundle.chapters.find((c: ChapterRow) => c.id === parsed.data.chapterId) + if (!chapter) return res.status(404).json({ error: '章节不存在' }) + const ctx = buildProseContext(bundle, chapter) + void streamStage(req, res, 'prose', bundle.project.id, withRequirement(proseMessages(ctx), parsed.data.requirement)) +}) + +// ---------- 段落重写(流式) ---------- +generateRouter.post('/generate/rewrite', (req, res) => { + const parsed = z + .object({ + projectId: z.number().int(), + mode: z.enum(['rewrite', 'expand', 'shrink', 'custom']), + instruction: z.string().max(1000).optional(), + selected: z.string().min(1).max(20000), + before: z.string().max(1000).default(''), + after: z.string().max(1000).default('') + }) + .safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: parsed.error.issues[0].message }) + void streamStage(req, res, `rewrite:${parsed.data.mode}`, parsed.data.projectId, rewriteMessages(parsed.data)) +}) + +// ================= 采纳落库 ================= + +// ---------- 为旧人物卡补写专业字段(声口 / 内在冲突 / 秘密),只填空字段不覆盖已填 ---------- +generateRouter.post('/generate/backfill/characters', async (req, res) => { + const parsed = z.object({ projectId: z.number().int(), requirement: z.string().max(3000).optional() }).safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: '参数错误' }) + const bundle = loadBundle(parsed.data.projectId) + if (!bundle) return res.status(404).json({ error: '项目不存在' }) + const targets = bundle.characters.filter((c) => !c.speech_pattern.trim() || !c.inner_conflict.trim() || !c.secret.trim()) + if (targets.length === 0) return res.json({ updated: 0, message: '所有人物卡均已补全' }) + + const cfg = getLlmConfig() + const messages = withRequirement( + characterBackfillMessages( + targets.map((c) => ({ name: c.name, role: c.role, aliases: c.aliases, personality: c.personality, background: c.background, goals: c.goals })), + toProjectLike(bundle.project) + ), + parsed.data.requirement + ) + try { + const { data, usage } = await chatStructured(cfg, messages, (t) => parseStructured(t, characterBackfillArraySchema), { + signal: AbortSignal.timeout(180000) + }) + let updated = 0 + db.exec('BEGIN') + try { + for (const item of data) { + const hit = targets.find((c) => c.name === item.name || (c.aliases && c.aliases.split(/[、,,;;\s]+/).includes(item.name))) + if (!hit) continue + const sets: string[] = [] + const vals: SQLInputValue[] = [] + if (!hit.speech_pattern.trim() && item.speech_pattern.trim()) { + sets.push('speech_pattern=?') + vals.push(item.speech_pattern) + } + if (!hit.inner_conflict.trim() && item.inner_conflict.trim()) { + sets.push('inner_conflict=?') + vals.push(item.inner_conflict) + } + if (!hit.secret.trim() && item.secret.trim()) { + sets.push('secret=?') + vals.push(item.secret) + } + if (sets.length === 0) continue + vals.push(now(), hit.id) + db.prepare(`UPDATE characters SET ${sets.join(', ')}, updated_at=? WHERE id=?`).run(...vals) + updated++ + } + db.exec('COMMIT') + } catch (e) { + db.exec('ROLLBACK') + throw e + } + db.prepare('INSERT INTO generation_logs (project_id, stage, model, prompt_tokens, completion_tokens, ok, created_at) VALUES (?,?,?,?,?,1,?)').run( + bundle.project.id, + 'backfill:characters', + cfg.model, + usage?.prompt_tokens ?? 0, + usage?.completion_tokens ?? 0, + now() + ) + res.json({ updated, filled: data.length }) + } catch (e) { + const msg = e instanceof Error ? e.message : String(e) + db.prepare('INSERT INTO generation_logs (project_id, stage, model, ok, created_at) VALUES (?,?,?,0,?)').run(bundle.project.id, 'backfill:characters', cfg.model, now()) + res.status(500).json({ error: msg }) + } +}) + +const applyCharacters = z.object({ projectId: z.number().int(), items: characterCardArraySchema }) +generateRouter.post('/generate/apply/characters', (req, res) => { + const parsed = applyCharacters.safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: '人物数据格式错误' }) + const { projectId, items } = parsed.data + const base = (db.prepare('SELECT COALESCE(MAX(sort),0) AS m FROM characters WHERE project_id = ?').get(projectId) as { m: number }).m + db.exec('BEGIN') + try { + items.forEach((c, i) => { + db.prepare( + 'INSERT INTO characters (project_id, name, aliases, role, appearance, personality, background, goals, speech_pattern, inner_conflict, secret, arc_notes, sort, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)' + ).run( + projectId, + c.name, + c.aliases, + c.role, + c.appearance, + c.personality, + c.background, + c.goals, + c.speech_pattern, + c.inner_conflict, + c.secret, + c.arc_notes, + base + i + 1, + now() + ) + }) + db.exec('COMMIT') + } catch (e) { + db.exec('ROLLBACK') + throw e + } + res.json({ inserted: items.length }) +}) + +const applyWorld = z.object({ projectId: z.number().int(), items: worldEntryArraySchema }) +generateRouter.post('/generate/apply/world', (req, res) => { + const parsed = applyWorld.safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: '设定数据格式错误' }) + const { projectId, items } = parsed.data + const base = (db.prepare('SELECT COALESCE(MAX(sort),0) AS m FROM world_entries WHERE project_id = ?').get(projectId) as { m: number }).m + db.exec('BEGIN') + try { + items.forEach((w, i) => { + db.prepare('INSERT INTO world_entries (project_id, category, title, content, sort, updated_at) VALUES (?,?,?,?,?,?)').run( + projectId, + w.category, + w.title, + w.content, + base + i + 1, + now() + ) + }) + db.exec('COMMIT') + } catch (e) { + db.exec('ROLLBACK') + throw e + } + res.json({ inserted: items.length }) +}) + +const applyVolumes = z.object({ projectId: z.number().int(), items: volumeArraySchema }) +generateRouter.post('/generate/apply/volumes', (req, res) => { + const parsed = applyVolumes.safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: '大纲数据格式错误' }) + const { projectId } = parsed.data + const items = dedupeByTitle(parsed.data.items, (v) => v.title, 'vol') + // 重建大纲:删除旧卷与细纲;无内容的空章节一并清除,已写正文的章节保留为「未分卷」 + db.exec('BEGIN') + try { + db.prepare("DELETE FROM chapters WHERE project_id = ? AND status = 'empty'").run(projectId) + db.prepare('DELETE FROM beats WHERE project_id = ?').run(projectId) + db.prepare('DELETE FROM volumes WHERE project_id = ?').run(projectId) + items.forEach((v, i) => { + db.prepare('INSERT INTO volumes (project_id, title, summary, sort) VALUES (?,?,?,?)').run(projectId, v.title, v.summary, i + 1) + }) + db.exec('COMMIT') + } catch (e) { + db.exec('ROLLBACK') + throw e + } + res.json({ inserted: items.length }) +}) + +const applyBeats = z.object({ projectId: z.number().int(), volumeId: z.number().int(), items: beatArraySchema }) +generateRouter.post('/generate/apply/beats', (req, res) => { + const parsed = applyBeats.safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: '细纲数据格式错误' }) + const { projectId, volumeId } = parsed.data + const items = dedupeByTitle(parsed.data.items, (b) => b.title, 'beat') + const bundle = loadBundle(projectId) + if (!bundle) return res.status(404).json({ error: '项目不存在' }) + const volume = bundle.volumes.find((v: VolumeRow) => v.id === volumeId) + if (!volume) return res.status(404).json({ error: '卷不存在' }) + + // 全书全局排序:此前各卷章数之和作为本卷起始序号 + const sortedVolumes = [...bundle.volumes].sort((a, b) => a.sort - b.sort) + const beforeCount = sortedVolumes + .slice(0, sortedVolumes.findIndex((v) => v.id === volumeId)) + .reduce((acc, v) => acc + bundle.chapters.filter((c: ChapterRow) => c.volume_id === v.id).length, 0) + + db.exec('BEGIN') + try { + db.prepare('DELETE FROM beats WHERE volume_id = ?').run(volumeId) + // 清掉本卷空章节(内容章节保留,beat 关联自动断开) + db.prepare("DELETE FROM chapters WHERE volume_id = ? AND status = 'empty'").run(volumeId) + items.forEach((b, i) => { + const involvedIds = mapNamesToIds(b.involved_characters, bundle.characters) + const info = db + .prepare( + 'INSERT INTO beats (project_id, volume_id, sort, title, events, involved_characters, foreshadow_plan, pov, emotional_arc, purpose, tension) VALUES (?,?,?,?,?,?,?,?,?,?,?)' + ) + .run(projectId, volumeId, i + 1, b.title, b.events, JSON.stringify(involvedIds), b.foreshadow_plan, b.pov ?? '', b.emotional_arc ?? '', b.purpose ?? '', b.tension ?? 0) + db.prepare('INSERT INTO chapters (project_id, beat_id, volume_id, sort, title, updated_at) VALUES (?,?,?,?,?,?)').run( + projectId, + info.lastInsertRowid, + volumeId, + beforeCount + i + 1, + b.title, + now() + ) + }) + db.exec('COMMIT') + } catch (e) { + db.exec('ROLLBACK') + throw e + } + res.json({ inserted: items.length }) +}) diff --git a/server/src/routes/outline.ts b/server/src/routes/outline.ts new file mode 100644 index 0000000..bf20d76 --- /dev/null +++ b/server/src/routes/outline.ts @@ -0,0 +1,119 @@ +import { Router } from 'express' +import { z } from 'zod' +import { db } from '../db/connection.js' +import { parseInvolved } from '../db/types.js' + +export const outlineRouter = Router() + +// ---------- 分卷 ---------- +const volumeInput = z.object({ + title: z.string().min(1).max(100), + summary: z.string().max(4000).default(''), + sort: z.number().int().default(0) +}) + +outlineRouter.post('/projects/:projectId/volumes', (req, res) => { + const projectId = Number(req.params.projectId) + const parsed = volumeInput.safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: parsed.error.issues[0].message }) + const info = db + .prepare('INSERT INTO volumes (project_id, title, summary, sort) VALUES (?,?,?,?)') + .run(projectId, parsed.data.title, parsed.data.summary, parsed.data.sort) + res.json(db.prepare('SELECT * FROM volumes WHERE id = ?').get(info.lastInsertRowid)) +}) + +outlineRouter.patch('/volumes/:id', (req, res) => { + const parsed = volumeInput.partial().safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: parsed.error.issues[0].message }) + const entries = Object.entries(parsed.data).filter(([, v]) => v !== undefined) + if (entries.length === 0) return res.status(400).json({ error: '无更新字段' }) + db.prepare(`UPDATE volumes SET ${entries.map(([c]) => `${c} = ?`).join(', ')} WHERE id = ?`).run( + ...entries.map(([, v]) => v), + Number(req.params.id) + ) + res.json(db.prepare('SELECT * FROM volumes WHERE id = ?').get(Number(req.params.id))) +}) + +// 删除卷:细纲与章节保留(volume_id 置空) +outlineRouter.delete('/volumes/:id', (req, res) => { + db.prepare('DELETE FROM volumes WHERE id = ?').run(Number(req.params.id)) + res.json({ ok: true }) +}) + +// ---------- 分章细纲 ---------- +const beatInput = z.object({ + volume_id: z.number().int().nullable().default(null), + title: z.string().min(1).max(120), + events: z.string().max(12000).default(''), + involved_characters: z.array(z.number().int()).default([]), + foreshadow_plan: z.string().max(4000).default(''), + pov: z.string().max(60).default(''), + emotional_arc: z.string().max(200).default(''), + purpose: z.string().max(30).default(''), + tension: z.number().int().min(0).max(5).default(0) +}) + +outlineRouter.post('/projects/:projectId/beats', (req, res) => { + const projectId = Number(req.params.projectId) + const parsed = beatInput.safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: parsed.error.issues[0].message }) + const d = parsed.data + const maxSort = db.prepare('SELECT COALESCE(MAX(sort),0) AS m FROM beats WHERE project_id = ?').get(projectId) as { m: number } + const sort = d.volume_id + ? (db.prepare('SELECT COALESCE(MAX(sort),0) AS m FROM beats WHERE volume_id = ?').get(d.volume_id) as { m: number }).m + 1 + : maxSort.m + 1 + const info = db + .prepare( + 'INSERT INTO beats (project_id, volume_id, sort, title, events, involved_characters, foreshadow_plan, pov, emotional_arc, purpose, tension) VALUES (?,?,?,?,?,?,?,?,?,?,?)' + ) + .run(projectId, d.volume_id, sort, d.title, d.events, JSON.stringify(d.involved_characters), d.foreshadow_plan, d.pov, d.emotional_arc, d.purpose, d.tension) + res.json(parseBeatRow(db.prepare('SELECT * FROM beats WHERE id = ?').get(info.lastInsertRowid))) +}) + +outlineRouter.patch('/beats/:id', (req, res) => { + const parsed = beatInput.partial().safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: parsed.error.issues[0].message }) + const entries = Object.entries(parsed.data).filter(([, v]) => v !== undefined) as [string, string | number | null][] + if (entries.length === 0) return res.status(400).json({ error: '无更新字段' }) + // involved_characters 以 JSON 字符串入库 + const mapped = entries.map(([k, v]) => (k === 'involved_characters' ? (['involved_characters', JSON.stringify(v)] as const) : ([k, v] as const))) + db.prepare(`UPDATE beats SET ${mapped.map(([c]) => `${c} = ?`).join(', ')} WHERE id = ?`).run( + ...mapped.map(([, v]) => v), + Number(req.params.id) + ) + res.json(parseBeatRow(db.prepare('SELECT * FROM beats WHERE id = ?').get(Number(req.params.id)))) +}) + +/** 出站形态:involved_characters 解析为数组 */ +function parseBeatRow(row: unknown) { + const r = row as { involved_characters: string } | undefined + if (!r) return row + try { + const arr = JSON.parse(r.involved_characters) + return { ...r, involved_characters: Array.isArray(arr) ? arr : [] } + } catch { + return { ...r, involved_characters: [] } + } +} + +outlineRouter.delete('/beats/:id', (req, res) => { + db.prepare('DELETE FROM beats WHERE id = ?').run(Number(req.params.id)) + res.json({ ok: true }) +}) + +/** 工具:把模型输出的涉及人物名映射为人物 id */ +export function mapNamesToIds(names: string[], characters: { id: number; name: string; aliases: string }[]): number[] { + const ids: number[] = [] + for (const raw of names) { + const name = String(raw).trim() + if (!name) continue + const hit = + characters.find((c) => c.name === name) ?? + characters.find((c) => c.aliases && c.aliases.split(/[、,,;;\s]+/).includes(name)) ?? + characters.find((c) => c.name.includes(name) || name.includes(c.name)) + if (hit && !ids.includes(hit.id)) ids.push(hit.id) + } + return ids +} + +export { parseInvolved } diff --git a/server/src/routes/projects.ts b/server/src/routes/projects.ts new file mode 100644 index 0000000..ff552cb --- /dev/null +++ b/server/src/routes/projects.ts @@ -0,0 +1,80 @@ +import { Router } from 'express' +import { z } from 'zod' +import { db, now } from '../db/connection.js' +import { loadBundle } from '../pipeline/context.js' + +export const projectsRouter = Router() + +const projectInput = z.object({ + name: z.string().min(1, '作品名不能为空').max(200), + genre: z.string().max(100).default(''), + premise: z.string().max(60000, '故事创意过长(上限 6 万字符)').default(''), + style_notes: z.string().max(20000).default(''), + style_sample: z.string().max(40000).default(''), + narrative_pov: z.string().max(100).default(''), + taboos: z.string().max(2000).default(''), + target_chapter_words: z.number().int().min(200).max(20000).default(3000), + target_chapter_count: z.number().int().min(1).max(2000).default(30) +}) + +projectsRouter.get('/projects', (_req, res) => { + const rows = db + .prepare( + `SELECT p.*, + COALESCE((SELECT SUM(word_count) FROM chapters c WHERE c.project_id = p.id), 0) AS total_words, + (SELECT COUNT(*) FROM chapters c WHERE c.project_id = p.id) AS chapter_count, + (SELECT COUNT(*) FROM chapters c WHERE c.project_id = p.id AND c.content != '') AS written_count + FROM projects p ORDER BY p.updated_at DESC` + ) + .all() + res.json(rows) +}) + +projectsRouter.post('/projects', (req, res) => { + const parsed = projectInput.safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: parsed.error.issues[0].message }) + const d = parsed.data + const t = now() + const r = db + .prepare( + 'INSERT INTO projects (name, genre, premise, style_notes, style_sample, narrative_pov, taboos, target_chapter_words, target_chapter_count, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)' + ) + .run(d.name, d.genre, d.premise, d.style_notes, d.style_sample, d.narrative_pov, d.taboos, d.target_chapter_words, d.target_chapter_count, t, t) + res.json(db.prepare('SELECT * FROM projects WHERE id = ?').get(r.lastInsertRowid)) +}) + +projectsRouter.patch('/projects/:id', (req, res) => { + const id = Number(req.params.id) + const parsed = projectInput.partial().safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: parsed.error.issues[0].message }) + const cur = db.prepare('SELECT * FROM projects WHERE id = ?').get(id) as Record | undefined + if (!cur) return res.status(404).json({ error: '项目不存在' }) + const d = { ...cur, ...parsed.data, updated_at: now() } + db.prepare( + 'UPDATE projects SET name=?, genre=?, premise=?, style_notes=?, style_sample=?, narrative_pov=?, taboos=?, target_chapter_words=?, target_chapter_count=?, updated_at=? WHERE id=?' + ).run( + d.name as string, + d.genre as string, + d.premise as string, + d.style_notes as string, + d.style_sample as string, + d.narrative_pov as string, + d.taboos as string, + d.target_chapter_words as number, + d.target_chapter_count as number, + d.updated_at as string, + id + ) + res.json(db.prepare('SELECT * FROM projects WHERE id = ?').get(id)) +}) + +projectsRouter.delete('/projects/:id', (req, res) => { + db.prepare('DELETE FROM projects WHERE id = ?').run(Number(req.params.id)) + res.json({ ok: true }) +}) + +projectsRouter.get('/projects/:id/bundle', (req, res) => { + const bundle = loadBundle(Number(req.params.id)) + if (!bundle) return res.status(404).json({ error: '项目不存在' }) + res.json(bundle) +}) diff --git a/server/src/routes/settings.ts b/server/src/routes/settings.ts new file mode 100644 index 0000000..ef1a5ff --- /dev/null +++ b/server/src/routes/settings.ts @@ -0,0 +1,46 @@ +import { Router } from 'express' +import { z } from 'zod' +import { db, now } from '../db/connection.js' +import { getLlmConfig } from '../db/seed.js' +import { testConnection } from '../llm/client.js' + +export const settingsRouter = Router() + +const settingsInput = z.object({ + base_url: z.string().url().max(300), + api_key: z.string().max(300).default(''), + model: z.string().min(1).max(200), + temperature: z.number().min(0).max(2).default(0.8), + max_tokens: z.number().int().min(256).max(65536).default(12000) +}) + +settingsRouter.get('/settings', (_req, res) => { + const cfg = getLlmConfig() + res.json({ ...cfg, id: 1, updated_at: now() }) +}) + +settingsRouter.put('/settings', (req, res) => { + const parsed = settingsInput.safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: parsed.error.issues[0].message }) + const d = parsed.data + db.prepare('UPDATE llm_config SET base_url=?, api_key=?, model=?, temperature=?, max_tokens=?, updated_at=? WHERE id=1').run( + d.base_url, + d.api_key, + d.model, + d.temperature, + d.max_tokens, + now() + ) + res.json(getLlmConfig()) +}) + +settingsRouter.post('/settings/test', async (req, res) => { + const parsed = z.object({ base_url: z.string().url(), api_key: z.string(), model: z.string().min(1) }).safeParse(req.body) + if (!parsed.success) return res.status(400).json({ error: parsed.error.issues[0].message }) + try { + const result = await testConnection(parsed.data) + res.json({ ok: true, ...result }) + } catch (e) { + res.json({ ok: false, error: e instanceof Error ? e.message : String(e) }) + } +}) diff --git a/server/src/util/epub.ts b/server/src/util/epub.ts new file mode 100644 index 0000000..9f0fac7 --- /dev/null +++ b/server/src/util/epub.ts @@ -0,0 +1,103 @@ +import JSZip from 'jszip' + +export interface EpubChapter { + volumeTitle?: string + title: string + paragraphs: string[] +} + +const esc = (s: string) => + s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''') + +const CSS = `body{font-family:serif;line-height:1.8;margin:1em}h1{font-size:1.4em;text-align:center}h2{font-size:1.2em;text-align:center}p{text-indent:2em;margin:0.5em 0}` + +/** 手工构建 EPUB3(mimetype 首位 + 容器 + OPF + 导航 + 章节),零不确定依赖 */ +export async function buildEpub(title: string, chapters: EpubChapter[]): Promise { + const zip = new JSZip() + zip.file('mimetype', 'application/epub+zip', { compression: 'STORE' }) + zip.file( + 'META-INF/container.xml', + ` + + +` + ) + zip.file('OEBPS/style.css', CSS) + + // 卷分组导航 + const groups: { volume?: string; chapters: { idx: number; title: string }[] }[] = [] + for (let i = 0; i < chapters.length; i++) { + const c = chapters[i] + const last = groups[groups.length - 1] + if (last && last.volume === c.volumeTitle) last.chapters.push({ idx: i, title: c.title }) + else groups.push({ volume: c.volumeTitle, chapters: [{ idx: i, title: c.title }] }) + } + + const chapterFiles = chapters.map((_, i) => `chapter_${String(i + 1).padStart(4, '0')}.xhtml`) + chapters.forEach((c, i) => { + const volLine = c.volumeTitle ? `

${esc(c.volumeTitle)}

` : '' + zip.file( + `OEBPS/${chapterFiles[i]}`, + ` + +${esc(c.title)} +

${esc(c.title)}

${volLine}${c.paragraphs.map((p) => `

${esc(p)}

`).join('')}` + ) + }) + + const navLis = groups + .map((g) => { + const label = esc(g.volume || '正文') + const items = g.chapters + .map((ch) => `
  • ${esc(ch.title)}
  • `) + .join('') + return g.volume ? `
  • ${label}
      ${items}
  • ` : items + }) + .join('') + + zip.file( + 'OEBPS/nav.xhtml', + ` + +目录 +` + ) + + zip.file( + 'OEBPS/title.xhtml', + ` + +${esc(title)} +

    ${esc(title)}

    ` + ) + + const manifest = [ + '', + '', + '', + ...chapterFiles.map((f, i) => ``) + ].join('\n ') + const spine = ['', '', ...chapterFiles.map((_, i) => ``)].join('\n ') + const uuid = crypto.randomUUID() + + zip.file( + 'OEBPS/content.opf', + ` + + + urn:uuid:${uuid} + ${esc(title)} + zh-CN + ${new Date().toISOString().replace(/\.\d+Z$/, 'Z')} + + + ${manifest} + + + ${spine} + +` + ) + + return zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' }) +} diff --git a/server/src/util/json.ts b/server/src/util/json.ts new file mode 100644 index 0000000..0b00e9f --- /dev/null +++ b/server/src/util/json.ts @@ -0,0 +1,24 @@ +import type { ZodTypeAny, z } from 'zod' + +/** 从模型输出中宽松地提取 JSON(容忍 markdown 代码块、前后缀文字) */ +export function parseJsonLoose(text: string): unknown { + let t = text.trim() + const fence = t.match(/```(?:json)?\s*([\s\S]*?)```/) + if (fence) t = fence[1].trim() + const starts = ['{', '['].map((c) => t.indexOf(c)).filter((i) => i !== -1) + if (starts.length === 0) throw new Error('输出中未找到 JSON') + const start = Math.min(...starts) + const end = Math.max(t.lastIndexOf('}'), t.lastIndexOf(']')) + if (end <= start) throw new Error('JSON 不完整') + return JSON.parse(t.slice(start, end + 1)) +} + +/** 解析 + zod 校验,失败抛出可读错误 */ +export function parseStructured(text: string, schema: S): z.output { + const raw = parseJsonLoose(text) + const parsed = schema.safeParse(raw) + if (!parsed.success) { + throw new Error('JSON 结构校验失败: ' + parsed.error.issues.slice(0, 3).map((i) => `${i.path.join('.')} ${i.message}`).join('; ')) + } + return parsed.data +} diff --git a/server/src/util/sse.ts b/server/src/util/sse.ts new file mode 100644 index 0000000..0010b15 --- /dev/null +++ b/server/src/util/sse.ts @@ -0,0 +1,15 @@ +import type { Response } from 'express' + +export function sseInit(res: Response) { + res.status(200).set({ + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + 'X-Accel-Buffering': 'no' + }) + res.flushHeaders?.() +} + +export function sseSend(res: Response, event: string, data: unknown) { + res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`) +} diff --git a/server/src/util/words.ts b/server/src/util/words.ts new file mode 100644 index 0000000..ea48660 --- /dev/null +++ b/server/src/util/words.ts @@ -0,0 +1,4 @@ +/** 中文友好的字数统计:去除所有空白后的字符数 */ +export function countWords(s: string): number { + return [...s.replace(/\s+/g, '')].length +} diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 0000000..ebf301a --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["src"] +} diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..217d465 --- /dev/null +++ b/web/index.html @@ -0,0 +1,12 @@ + + + + + + 智能小说工作台 + + +
    + + + diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..5f0c3c4 --- /dev/null +++ b/web/package.json @@ -0,0 +1,24 @@ +{ + "name": "web", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc --noEmit -p tsconfig.json && vite build", + "preview": "vite preview" + }, + "dependencies": { + "antd": "^5.22.2", + "dayjs": "^1.11.13", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.7.2", + "vite": "^6.0.3" + } +} diff --git a/web/src/App.tsx b/web/src/App.tsx new file mode 100644 index 0000000..d9db918 --- /dev/null +++ b/web/src/App.tsx @@ -0,0 +1,27 @@ +import { ConfigProvider } from 'antd' +import zhCN from 'antd/locale/zh_CN' +import dayjs from 'dayjs' +import 'dayjs/locale/zh-cn' +import { BrowserRouter, Route, Routes } from 'react-router-dom' +import HomePage from './pages/HomePage' +import WorkbenchPage from './pages/WorkbenchPage' + +dayjs.locale('zh-cn') + +export default function App() { + return ( + + + + } /> + } /> + + + + ) +} diff --git a/web/src/api/client.ts b/web/src/api/client.ts new file mode 100644 index 0000000..b0a223e --- /dev/null +++ b/web/src/api/client.ts @@ -0,0 +1,93 @@ +async function request(path: string, method: string, body?: unknown): Promise { + const res = await fetch(path, { + method, + headers: body !== undefined ? { 'Content-Type': 'application/json' } : undefined, + body: body !== undefined ? JSON.stringify(body) : undefined + }) + if (!res.ok) { + let msg = `HTTP ${res.status}` + try { + const j = await res.json() + if (j?.error) msg = j.error + } catch { + /* ignore */ + } + throw new Error(msg) + } + return res.json() as Promise +} + +export const apiGet = (p: string) => request(p, 'GET') +export const apiPost = (p: string, body?: unknown) => request(p, 'POST', body ?? {}) +export const apiPatch = (p: string, body: unknown) => request(p, 'PATCH', body) +export const apiPut = (p: string, body: unknown) => request(p, 'PUT', body) +export const apiDelete = (p: string) => request(p, 'DELETE') + +export interface SseDoneMessage { + content?: string + data?: unknown + usage?: { prompt_tokens?: number; completion_tokens?: number } +} + +export interface SseHandlers { + signal?: AbortSignal + onDelta?: (t: string) => void + onDone?: (m: SseDoneMessage) => void + onError?: (msg: string) => void +} + +/** POST + SSE 流式响应解析(delta/done/error 三种事件) */ +export async function ssePost(path: string, body: unknown, h: SseHandlers): Promise { + let res: Response + try { + res = await fetch(path, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + signal: h.signal + }) + } catch (e) { + if ((e as Error).name !== 'AbortError') h.onError?.((e as Error).message) + return + } + if (!res.ok || !res.body) { + let msg = `HTTP ${res.status}` + try { + const j = await res.json() + if (j?.error) msg = j.error + } catch { + /* ignore */ + } + h.onError?.(msg) + return + } + const reader = res.body.getReader() + const decoder = new TextDecoder() + let buf = '' + for (;;) { + const { done, value } = await reader.read() + if (done) break + buf += decoder.decode(value, { stream: true }) + let idx: number + while ((idx = buf.indexOf('\n\n')) !== -1) { + const chunk = buf.slice(0, idx) + buf = buf.slice(idx + 2) + let event = 'message' + let data = '' + for (const line of chunk.split('\n')) { + if (line.startsWith('event: ')) event = line.slice(7) + else if (line.startsWith('data: ')) data += line.slice(6) + } + if (!data) continue + let msg: Record + try { + msg = JSON.parse(data) + } catch { + continue + } + if (event === 'delta') h.onDelta?.(msg.text as string) + else if (event === 'done') h.onDone?.(msg as SseDoneMessage) + else if (event === 'error') h.onError?.(msg.message as string) + } + } +} diff --git a/web/src/api/types.ts b/web/src/api/types.ts new file mode 100644 index 0000000..45cf1fa --- /dev/null +++ b/web/src/api/types.ts @@ -0,0 +1,147 @@ +export interface Project { + id: number + name: string + genre: string + premise: string + style_notes: string + style_sample: string + narrative_pov: string + taboos: string + target_chapter_words: number + target_chapter_count: number + created_at: string + updated_at: string + total_words?: number + chapter_count?: number + written_count?: number +} + +export interface Character { + id: number + project_id: number + name: string + aliases: string + role: string + appearance: string + personality: string + background: string + goals: string + speech_pattern: string + inner_conflict: string + secret: string + arc_notes: string + sort: number +} + +export interface WorldEntry { + id: number + project_id: number + category: string + title: string + content: string + sort: number +} + +export interface Volume { + id: number + project_id: number + title: string + summary: string + sort: number +} + +export interface Beat { + id: number + project_id: number + volume_id: number | null + sort: number + title: string + events: string + involved_characters: number[] + foreshadow_plan: string + pov: string + emotional_arc: string + purpose: string + tension: number +} + +export interface Chapter { + id: number + project_id: number + beat_id: number | null + volume_id: number | null + sort: number + title: string + content: string + status: 'empty' | 'generated' | 'edited' + summary: string + word_count: number + updated_at: string +} + +export interface Foreshadow { + id: number + project_id: number + description: string + planted_chapter_id: number | null + paid_chapter_id: number | null + status: 'planted' | 'paid' + notes: string +} + +export interface TimelineEvent { + id: number + project_id: number + chapter_id: number | null + story_time: string + title: string + description: string + sort: number +} + +export interface LlmSettings { + id: number + base_url: string + api_key: string + model: string + temperature: number + max_tokens: number +} + +export interface Bundle { + project: Project + characters: Character[] + world: WorldEntry[] + volumes: Volume[] + beats: Beat[] + chapters: Chapter[] + foreshadows: Foreshadow[] + timeline: TimelineEvent[] +} + +export interface PostprocessResult { + summary: string + new_world_facts: number + character_updates: number + new_foreshadows: number + paid_foreshadows: number + timeline_events: number +} + +export interface GenState { + stage: string + label: string + text: string + done: boolean +} + +export interface RewriteReq { + mode: 'rewrite' | 'expand' | 'shrink' | 'custom' + instruction?: string + selected: string + before: string + after: string + chapterId: number + start: number + end: number +} diff --git a/web/src/components/AutoSaveText.tsx b/web/src/components/AutoSaveText.tsx new file mode 100644 index 0000000..38751ff --- /dev/null +++ b/web/src/components/AutoSaveText.tsx @@ -0,0 +1,48 @@ +import { Input } from 'antd' +import { useEffect, useState } from 'react' + +/** 受控可编辑文本:外部值变化时同步,失焦时若变更则触发保存 */ +export default function AutoSaveText({ + value, + onSave, + multiline = false, + placeholder, + minRows = 2, + maxRows, + style, + bordered = true, + size +}: { + value: string + onSave: (next: string) => void + multiline?: boolean + placeholder?: string + minRows?: number + maxRows?: number + style?: React.CSSProperties + bordered?: boolean + size?: 'small' | 'middle' | 'large' +}) { + const [v, setV] = useState(value) + useEffect(() => setV(value), [value]) + + const commit = () => { + if (v !== value) onSave(v) + } + + const common = { + value: v, + placeholder, + variant: bordered ? ('outlined' as const) : ('borderless' as const), + size, + style, + onChange: (e: React.ChangeEvent) => setV(e.target.value), + onBlur: commit, + onPressEnter: (e: React.KeyboardEvent) => { + if (!multiline) { + ;(e.target as HTMLInputElement).blur() + } + } + } + return multiline ? : +} diff --git a/web/src/components/BeatsView.tsx b/web/src/components/BeatsView.tsx new file mode 100644 index 0000000..359de58 --- /dev/null +++ b/web/src/components/BeatsView.tsx @@ -0,0 +1,150 @@ +import { DeleteOutlined, ThunderboltOutlined, EditOutlined } from '@ant-design/icons' +import { Button, Card, Empty, Popconfirm, Rate, Select, Space, Tag, message } from 'antd' +import { apiDelete, apiPatch } from '../api/client' +import type { Bundle } from '../api/types' +import AutoSaveText from './AutoSaveText' + +const PURPOSES = ['开局', '铺垫', '升温', '转折', '高潮', '回落', '收束', '过渡'] + +/** 分章细纲:按卷查看/编辑每章的事件、人物与伏笔安排 */ +export default function BeatsView({ + bundle, + volumeId, + setVolumeId, + running, + onGenerate, + onChanged, + onWriteChapter +}: { + bundle: Bundle + volumeId: number | null + setVolumeId: (id: number | null) => void + running: boolean + onGenerate: (volumeId: number) => void + onChanged: () => void + onWriteChapter: (chapterId: number) => void +}) { + const volume = bundle.volumes.find((v) => v.id === volumeId) ?? bundle.volumes[0] + const activeId = volume?.id ?? null + const beats = bundle.beats.filter((b) => b.volume_id === activeId).sort((a, b) => a.sort - b.sort) + const saveBeat = (id: number, patch: Record) => apiPatch(`/api/beats/${id}`, patch).catch((e) => message.error(e.message)) + + if (bundle.volumes.length === 0) { + return + } + + return ( +
    +
    + saveBeat(b.id, { pov: v ?? '' })} + options={bundle.characters.map((c) => ({ value: c.name, label: c.name }))} + /> + 功能 + saveBeat(b.id, { involved_characters: ids as number[] })} + options={bundle.characters.map((c) => ({ value: c.id, label: `${c.name}(${c.role})` }))} + /> +
    + saveBeat(b.id, { foreshadow_plan: t })} + placeholder="伏笔安排(埋下 / 回收)" + bordered={false} + style={{ color: '#9966cc' }} + /> +
    + + + ) + }) + )} +
    + ) +} diff --git a/web/src/components/ChapterEditor.tsx b/web/src/components/ChapterEditor.tsx new file mode 100644 index 0000000..6e4789e --- /dev/null +++ b/web/src/components/ChapterEditor.tsx @@ -0,0 +1,298 @@ +import { CheckCircleOutlined, EditOutlined, ExpandOutlined, LeftOutlined, RightOutlined, ScissorOutlined, ThunderboltOutlined } from '@ant-design/icons' +import { Button, Empty, Input, Popover, Select, Space, Tag, Tooltip, Typography, message } from 'antd' +import { useEffect, useMemo, useRef, useState } from 'react' +import type { Bundle, Chapter, RewriteReq } from '../api/types' + +/** 章节正文编辑器:自动保存 + 选中段落重写/扩写/缩写 + 章节快速导航 */ +export default function ChapterEditor({ + bundle, + chapter, + running, + onPatchChapter, + onGenerateProse, + onRewrite, + onSwitchChapter +}: { + bundle: Bundle + chapter: Chapter | undefined + running: boolean + onPatchChapter: (patch: { id: number; content?: string; title?: string }) => Promise + onGenerateProse: (chapterId: number) => void + onRewrite: (req: RewriteReq) => void + onSwitchChapter: (chapterId: number) => void +}) { + const [content, setContent] = useState(chapter?.content ?? '') + const [title, setTitle] = useState(chapter?.title ?? '') + const [sel, setSel] = useState<{ start: number; end: number } | null>(null) + const [customOpen, setCustomOpen] = useState(false) + const [customText, setCustomText] = useState('') + const taRef = useRef(null) + const timerRef = useRef | null>(null) + const dirtyRef = useRef(false) + const contentRef = useRef(content) + const chapterIdRef = useRef(chapter?.id) + + const chapterId = chapter?.id + + useEffect(() => { + contentRef.current = content + }, [content]) + + // 切换章节:重置本地编辑态;离开前把未保存内容立即落库 + useEffect(() => { + chapterIdRef.current = chapterId + setContent(chapter?.content ?? '') + setTitle(chapter?.title ?? '') + dirtyRef.current = false + return () => { + if (timerRef.current) clearTimeout(timerRef.current) + if (dirtyRef.current && chapterIdRef.current) { + dirtyRef.current = false + void onPatchChapter({ id: chapterIdRef.current, content: contentRef.current }) + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [chapterId]) + + // 外部内容更新(如采纳生成结果)时同步;用户正在编辑则不打扰 + useEffect(() => { + if (!dirtyRef.current) setContent(chapter?.content ?? '') + }, [chapter?.content]) + + const beat = useMemo(() => (chapter?.beat_id ? bundle.beats.find((b) => b.id === chapter.beat_id) : undefined), [bundle.beats, chapter?.beat_id]) + const chapterNo = useMemo(() => bundle.chapters.findIndex((c) => c.id === chapterId) + 1, [bundle.chapters, chapterId]) + + // 章节快速导航 + const idx = useMemo(() => bundle.chapters.findIndex((c) => c.id === chapterId), [bundle.chapters, chapterId]) + const prevChapter = idx > 0 ? bundle.chapters[idx - 1] : null + const nextChapter = idx >= 0 && idx < bundle.chapters.length - 1 ? bundle.chapters[idx + 1] : null + const chapterOptions = useMemo( + () => + bundle.chapters.map((c, i) => ({ + value: c.id, + // 细纲生成的标题通常已带「第N章」前缀,避免重复编号 + label: /^第\s*\d+\s*章/.test(c.title) ? c.title : `第${i + 1}章 ${c.title}` + })), + [bundle.chapters] + ) + + const scheduleSave = (next: string) => { + dirtyRef.current = true + if (timerRef.current) clearTimeout(timerRef.current) + if (!chapterId) return + timerRef.current = setTimeout(() => { + onPatchChapter({ id: chapterId, content: next }) + .then(() => { + if (contentRef.current === next) dirtyRef.current = false + }) + .catch((e) => message.error('保存失败:' + e.message)) + }, 800) + } + + const flushSave = async () => { + if (timerRef.current) clearTimeout(timerRef.current) + if (chapterId && dirtyRef.current) { + dirtyRef.current = false + await onPatchChapter({ id: chapterId, content }) + } + } + + if (!chapter) { + return ( +
    + +
    + ) + } + + const captureSelection = () => { + const ta = taRef.current + if (!ta) return + const { selectionStart, selectionEnd } = ta + setSel(selectionEnd > selectionStart ? { start: selectionStart, end: selectionEnd } : null) + } + + const doRewrite = (mode: RewriteReq['mode'], instruction?: string) => { + if (!sel || !content.slice(sel.start, sel.end).trim()) return + void flushSave().then(() => { + onRewrite({ + mode, + instruction, + selected: content.slice(sel.start, sel.end), + before: content.slice(Math.max(0, sel.start - 300), sel.start), + after: content.slice(sel.end, sel.end + 300), + chapterId: chapter.id, + start: sel.start, + end: sel.end + }) + }) + } + + const selectedText = sel ? content.slice(sel.start, sel.end) : '' + + return ( +
    +
    + + + +
    + + {beat && ( +
    + 本章细纲: + + {beat.events || '(无事件描述)'} + +
    + {beat.pov && 视角:{beat.pov}} + {beat.purpose && 功能:{beat.purpose}} + {beat.tension > 0 && ( + + 张力:{'●'.repeat(beat.tension)}{'○'.repeat(5 - beat.tension)} + + )} + {beat.emotional_arc && 情绪:{beat.emotional_arc}} + {beat.foreshadow_plan && 伏笔:{beat.foreshadow_plan}} +
    +
    + )} + + {sel && selectedText.trim() && ( +
    + + 已选中 {selectedText.trim().length} 字: + + + + + + + + + + + + + setCustomText(e.target.value)} + placeholder="例:改成第一人称;增加环境描写;语气更刻薄……" + /> + +
    + } + > + + + + 生成结果会出现在右侧抽屉中,确认后替换 +
    + )} + +