commit b913b8c78c074a57c74dc4325b3338c4c0a88dfc Author: ly1213 <1213887464@qq.com> Date: Mon Jun 8 19:00:03 2026 +0800 Initial upload for secondary development diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..302ec90 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Runtime/private data +.env +*.env +knowledge*.db +__pycache__/ + +# Dependencies +node_modules/ + +# Code signing certificates and private keys +certs/ +*.pfx +*.p12 +*.pvk +*.key +*.cer +*.crt + +# Build outputs +$dest/ +.backend-*/ +electron-launcher/dist/ +electron-launcher/build-resources/ +electron-launcher/build/ +chatlab-web/frontend/dist/ +chatlog_fastAPI/build/ +chatlog_fastAPI/dist/ + +# Keep only the latest shipped installer artifacts in release/ +release/* +!release/ChatLab-Setup-1.0.1-202605310641.exe +!release/ChatLab-Setup-1.0.1-202605310641.exe.blockmap +!release/manifest.txt diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3e084cb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,397 @@ +# 修改日志 CHANGELOG + +本文件记录每次代码修改的内容、原因与时间,精确到分钟,便于回溯和审查。 +**规则:每次修复 Bug 或新增功能,必须在此文件追加一条记录。** + +--- + +## [2026-05-11 01:15] 项目瘦身:清理分发包多余文件 + +### 背景 + +项目原始大小 2.3 GB,包含构建产物、Go SDK、PyInstaller 输出等开发专用文件,发送给用户体积过大。清理后保留"无痕启动控制台"完整运行所需的最小文件集。 + +### 删除内容 + +| 路径 | 大小 | 原因 | +|------|------|------| +| `chatlog_fastAPI/dist/` | 653 MB | PyInstaller 打包输出,不走此 exe 启动 | +| `chatlog_fastAPI/build/` | 206 MB | PyInstaller 中间产物 | +| `electron-launcher/dist/` | 349 MB | electron-builder 安装包,不走此包启动 | +| `go_env/` | 276 MB | 完整 Go SDK,chatlog.exe 已编译完毕 | +| `chatlab-web/frontend/dist/` | ~1 MB | Vite 构建输出,走 npm run dev | +| `cmd/` `internal/` `pkg/` `bin/` | <2 MB | Go 源码,不重新编译则无用 | +| `chatlab-web/backend/` | 49 KB | 废弃旧版 Python 后端 | +| `scripts/` `.github/` `.vscode/` | 小 | 开发专用 | +| `main.go` `go.mod` `go.sum` `Makefile` `.goreleaser.yaml` `.gitignore` | 小 | Go 构建文件 | +| `lib/windows_x64/wx_key.exp/.lib/.pdb` | 小 | 链接/调试符号,运行只需 .dll | +| `chatlog_fastAPI/__pycache__/` 等 | 28 KB | Python 字节码缓存,运行时自动重建 | +| `chatlog_fastAPI/chatlog.db` `knowledge.db` `*.spec` `test_wxid.py` `backend_design.md` | 小 | 空文件/调试/设计文档 | +| `test_audio*.py` `test_upload.go.bak` `launcher_error.log` | 小 | 临时调试文件 | +| `README(原).md` `chatlab.md` `dll调用指南.md` `启动与编译命令指南.md` | 小 | 开发参考文档 | +| `chatlog_fastAPI/knowledge_wxid_*.db` | ~224 KB | 个人账号知识库,用户登录后自动生成 | + +**节省:~1.49 GB(2.3 GB → 863 MB)** + +### 保留内容(最小运行集) + +``` +get_wechat_me/ +├── 无痕启动控制台.vbs 启动入口 +├── chatlog.exe Go 后端(63 MB) +├── CHANGELOG.md / README.md / DISCLAIMER.md / LICENSE +├── lib/windows_x64/wx_key.dll +├── electron-launcher/ +│ ├── main.js / preload.js / index.html / package.json / package-lock.json +│ └── node_modules/(650 MB,electron 二进制) +├── chatlog_fastAPI/ +│ ├── main.py / config.py / database.py / scheduler.py +│ ├── .env / requirements.txt +│ ├── routers/ / services/ +└── chatlab-web/frontend/ + ├── src/ / public/ / index.html / vite.config.js / package.json + └── node_modules/(150 MB,Vite + React) +``` + + + +## [2026-05-11 01:05] Bug 修复:可搜索下拉浮层背景透明 + +### 问题描述 + +上一轮新增的"添加群聊"可搜索下拉浮层背景完全透明,条目文字叠在底层内容上,可读性极差。 + +### 根因 + +代码中使用了 `var(--surface)` 和 `var(--surface-2)` 两个 CSS 变量,但 `index.css` 的 `:root` 从未定义这两个变量,浏览器 fallback 为 `transparent`。 +这两个变量在整个项目(`TopicsPage.jsx`、`KnowledgePage.jsx`、`MessageBubble.jsx`、`SettingsPage.jsx`、`App.jsx`)中被广泛引用。 + +### 修改文件明细 + +#### 1. `chatlab-web/frontend/src/index.css` + +**改动**:在 `:root` 的颜色变量区域追加两行别名定义: + +```css +/* 别名:兼容组件中使用的 var(--surface) / var(--surface-2) */ +--surface: #ffffff; /* 等同于 --bg-surface */ +--surface-2: #f0f2f5; /* 等同于 --bg-overlay */ +``` + +**原因**:项目现有的背景变量命名为 `--bg-surface`、`--bg-overlay` 等,但大量组件使用了更简短的 `--surface`/`--surface-2` 命名。根本修复是在 CSS 中统一补充定义这两个别名,一次修复所有引用,避免逐文件替换。 + +``` +位置:chatlab-web/frontend/src/index.css,:root 第 6 行后 +新增 3 行 +``` + +--- + +#### 2. `chatlab-web/frontend/src/pages/TopicsPage.jsx` + +**改动**:将浮层容器和条目中仍残留的错误变量名手动替换为正确值: +- 浮层容器:`var(--surface)` → `var(--bg-surface)` +- 条目默认背景:`transparent` → `var(--bg-surface)`(使其与浮层容器背景一致) +- 条目选中态:`var(--surface-2)` → `var(--bg-overlay)` +- `onMouseEnter`:`var(--surface-2)` → `var(--bg-hover)` +- `onMouseLeave`:`var(--surface-2)` → `var(--bg-overlay)` + +**原因**:在 CSS 补充别名定义之外,同步将浮层的引用改为语义更明确的 `--bg-*` 系列变量,确保悬停/选中状态的颜色层次感正确(白底 → 悬停浅灰 → 选中深灰)。 + +``` +位置:showGroupDropdown 浮层(第 315-340 行) +修改 5 处变量名 +``` + +--- + +### 总结 + +| 文件 | 改动性质 | 解决的问题 | +|------|----------|-----------| +| `chatlab-web/frontend/src/index.css` | Bug 修复 | 全局补充 `--surface`/`--surface-2` CSS 变量定义,消除所有相关透明背景问题 | +| `chatlab-web/frontend/src/pages/TopicsPage.jsx` | 变量名规范化 | 浮层下拉列表背景透明,条目悬停/选中状态无法区分 | + + + +--- + +## [2026-05-10 15:42] Bug 修复:新账号首次解密引导 + 账号切换前端数据刷新 + +### 问题描述 + +1. **新账号无法在启动器一键解密**:切换到从未登录过的新微信账号时,需要手动进入 chatlog.exe 的 TUI 界面执行解密,启动器无法自动引导。 +2. **账号切换后前端消息不刷新**:切换微信账号并重启控制台后,React 前端仍然显示上一个账号的会话列表和消息,没有切换到新账号的数据。 +3. **FastAPI 层账号数据库不切换**:`_resolved_wxid` 全局变量一旦解析成功后永不刷新,账号切换后 Python 业务层仍读取旧账号的知识库数据库。 + +--- + +### 修改文件明细 + +#### 1. `electron-launcher/main.js` + +**改动**:在 `keyProcess.on('close')` 回调中,读取完 `savedWorkDir` 后,新增对工作目录的检测逻辑: + +- 若工作目录不存在或目录内无 `.db` 文件 → 判定为"新账号首次解密",向渲染进程发送 `show-decrypt-dialog` IPC 事件,弹出引导弹窗。 +- 若工作目录已存在 `.db` 文件 → 判定为"已解密账号",直接启动 server,不弹窗。 + +**原因**:`chatlog.exe key` 命令执行完毕后会更新 `~/.chatlog/chatlog.json` 的 `last_account`,此时读取的 `savedWorkDir` 才是当前账号对应的工作目录。通过检测该目录是否含有解密产物(`.db` 文件),即可区分新旧账号。 + +``` +位置:electron-launcher/main.js,keyProcess.on('close') 内,第 251 行附近 +新增约 20 行代码 +``` + +--- + +#### 2. `electron-launcher/preload.js` + +**改动**:在 `contextBridge.exposeInMainWorld` 中新增一条 IPC 监听: + +```js +onShowDecryptDialog: (callback) => ipcRenderer.on('show-decrypt-dialog', () => callback()) +``` + +**原因**:Electron 的 contextIsolation 模式下,渲染进程无法直接访问 `ipcRenderer`,必须通过 `preload.js` 的 `contextBridge` 安全暴露。新增的 `show-decrypt-dialog` 事件需要在这里注册后,`index.html` 中的 JS 才能通过 `window.electronAPI.onShowDecryptDialog` 收到通知。 + +``` +位置:electron-launcher/preload.js,第 19-20 行 +新增 1 行 +``` + +--- + +#### 3. `electron-launcher/index.html` + +**改动(CSS 部分)**:新增模态弹窗相关样式(`.modal-overlay`、`.modal-box`、`.modal-title`、`.modal-body`、`.modal-steps`、`.modal-footer`、`.modal-spinner` 及其动画)。 + +**改动(HTML 部分)**:在 `` 顶部新增 `#decrypt-dialog` 模态弹窗元素,包含: +- 标题:"🔓 检测到新账号,正在解密数据" +- 操作步骤引导(切换到微信、点击聊天窗口、翻看历史消息) +- 与 chatlog.exe 一致的提示说明 +- 底部旋转 spinner + 计时显示 + +**改动(JS 部分)**: +- 新增 `decryptDialog`、`decryptDialogStatus`、`decryptDialogElapsed` DOM 引用。 +- `onDecryptStatus` 回调中额外同步更新弹窗计时器(`decryptDialogElapsed`)。 +- `onDecryptReady` 回调中增加 `decryptDialog.classList.remove('show')` 关闭弹窗。 +- 新增 `window.electronAPI.onShowDecryptDialog` 监听,收到事件时显示弹窗。 + +**原因**:用户切换新账号时需要按照 chatlog.exe TUI 中相同的操作提示(点击微信聊天界面)才能完成密钥提取。弹窗承担了原来需要手动进入 TUI 界面的提示职责,实现"一键解密"体验。 + +``` +位置:electron-launcher/index.html +新增 ~90 行 CSS + HTML + JS +``` + +--- + +#### 4. `chatlab-web/frontend/src/App.jsx` + +**改动**: + +1. **去掉 localStorage 冷加载旧账号数据**:将 `useState(() => loadSessionCache())` 改为 `useState([])`,避免启动时直接显示上一个账号的旧会话列表。 + +2. **新增账号指纹机制**: + - `calcFingerprint(sessions)` —— 取前 3 个会话 id 拼接成指纹字符串。 + - `loadAccountFingerprint()` / `saveAccountFingerprint(fp)` —— 读写 localStorage 中的 `chatlab_account_fingerprint` 键。 + +3. **`getSessions` 返回后检测账号切换**:`loadSessions` 函数(封装了原 `getSessions` 调用)在拿到新会话列表后,对比新旧指纹。若指纹不同,说明账号已切换,立即执行: + - `localStorage.removeItem(CACHE_KEY)` 清除旧缓存。 + - `setSelectedRoom(null)` 重置当前选中群(否则会拿旧群 id 去查询新账号的消息)。 + +4. **每 30 秒轮询一次**:新增 `useEffect` 设置 30 秒定时器,静默调用 `loadSessions(true)`(轮询模式不显示 loading 也不报错),以便在用户不刷新页面的情况下自动感知账号切换。 + +**原因**: +- 旧代码的 `getSessions()` 只在组件挂载时执行一次,账号切换不触发重新加载。 +- 旧代码从 localStorage 取缓存作为初始 state,导致旧账号数据在新账号登录时"闪现"。 +- 两处修复合力确保前端始终显示当前微信账号的真实数据。 + +``` +位置:chatlab-web/frontend/src/App.jsx +修改约 40 行,新增约 30 行 +``` + +--- + +#### 5. `chatlog_fastAPI/database.py` + +**改动**: + +1. 新增 `import time`。 +2. 新增全局变量 `_wxid_last_resolved: float = 0.0` 和常量 `_WXID_TTL = 60.0`。 +3. `get_current_wxid()` 函数:在命中现有缓存时增加时间判断 `(now - _wxid_last_resolved) < _WXID_TTL`,超过 60 秒强制重新解析。 +4. 在两处成功解析到 wxid 时,均更新 `_wxid_last_resolved = time.time()`。 + +**原因**:原代码中 `_resolved_wxid` 一旦解析成功就永久缓存,账号切换后 Python 层仍用旧 wxid 路由到旧知识库数据库文件(`knowledge_<旧wxid>.db`),导致 AI 话题分析、知识库搜索等功能数据错误。60 秒 TTL 平衡了性能与准确性。 + +``` +位置:chatlog_fastAPI/database.py,第 1-53 行 +修改约 10 行,新增约 5 行 +``` + +--- + +#### 6. `chatlog_fastAPI/main.py` + +**改动**: + +1. 新增 `import asyncio` 和 `import logging`。 +2. 新增后台协程 `_account_watch_loop()`:每 60 秒调用一次 `update_db_path()`,静默捕获异常。 +3. 在 `lifespan` 上下文管理器中,`yield` 前用 `asyncio.create_task` 启动该协程,`yield` 后取消任务并等待清理。 + +**原因**:仅靠 `database.py` 的 TTL 机制还不够——TTL 只在被调用时才重新检测,如果 FastAPI 长时间运行无请求则无法触发。后台轮询任务确保即使 FastAPI 在后台静默运行,账号切换也能在约 60 秒内被感知并切换数据库。 + +``` +位置:chatlog_fastAPI/main.py,全文 +修改约 5 行,新增约 15 行 +``` + +--- + +### 总结 + +| 文件 | 改动性质 | 解决的问题 | +|------|----------|-----------| +| `electron-launcher/main.js` | 功能新增 | 新账号自动检测 + 弹窗触发 | +| `electron-launcher/preload.js` | IPC 暴露 | 渲染进程收到新账号通知 | +| `electron-launcher/index.html` | UI 新增 | 首次解密引导弹窗,等同 chatlog.exe TUI 提示 | +| `chatlab-web/frontend/src/App.jsx` | Bug 修复 + 功能增强 | 账号切换后前端立即显示新账号数据 | +| `chatlog_fastAPI/database.py` | Bug 修复 | 账号切换后 Python 层自动切换知识库数据库 | +| `chatlog_fastAPI/main.py` | 功能新增 | 后台定期检测账号变化,无需依赖请求触发 | + +--- + +## [2026-05-11 00:32] Bug 修复:AI话题分析 & 知识库 4 个 UI 问题 + +### 问题描述 + +1. **AI话题分析"添加群聊"无搜索**:群聊列表使用原生 `` 替换为自定义可搜索组件:`` 搜索框 + 绝对定位浮层列表。 +- 新增 state:`groupSearchKeyword`、`showGroupDropdown`。 +- 浮层列表实时过滤(同时匹配群聊名称和群 ID),点击某项后填充输入框并收起浮层。 + +``` +位置:showAddGroup 块内(第 268 行附近) +新增约 45 行 JSX +``` + +**Bug 3A 修复 — AI分析进度条** + +- `handleInitGroup` 轮询中新增 `JSON.parse(task.progress)` 解析,将 `{processed, total}` 赋值到 `initProgress` state(初始化/完成时重置为 null)。 +- 在"AI 分析"按钮下方插入进度条 UI:显示 `processed/total` 数值 + 彩色进度条(宽度由百分比驱动,0.5s 过渡动画)。 +- 新增 state:`initProgress`。 + +``` +位置:handleInitGroup 函数(第 117 行)+ 按钮区域(第 336 行) +修改约 15 行,新增约 22 行 +``` + +**Bug 3B 修复 — AI生成知识文档进度条** + +- `handleSummarize` 改为接收后端返回的 `task_id`(`res.data.task_id`),若存在则用 `getTask(task_id)` 每 3 秒轮询,解析 `progress` 字段,更新 `summarizeProgress` state;若无 task_id 则 fallback 5 秒后刷新。 +- 在"AI 生成知识文档"按钮下方插入进度条 UI(与 3A 样式一致,宽度 180px)。 +- 新增导入:`getTask`(已在 `api/index.js` 中定义)。 +- 新增 state:`summarizeProgress`。 + +``` +位置:handleSummarize 函数(第 241 行)+ 按钮区域(第 422 行) +修改约 12 行,新增约 40 行 +``` + +**Bug 4 修复 — "已添加"改为红色"移除"按钮** + +- 管理消息弹窗左栏:将 `topicSeqs.has(Number(m.id))` 为真时显示的 `已添加` 改为红色 ``,点击调用已有的 `handleRemoveMsg(Number(m.id))`。 +- 样式:`color: 'var(--danger)'`,`borderColor: 'rgba(243,139,168,0.4)'`。 + +``` +位置:管理消息弹窗左栏消息列表(第 531 行) +修改 1 处,约 6 行 +``` + +--- + +#### 2. `chatlog_fastAPI/routers/knowledge.py` + +**Bug 2 后端 — SQL 联表返回 group_name** + +- 两处 `SELECT`(普通查询 + FTS 关键词查询)均扩展为: + - 新增 `LEFT JOIN groups g ON t.group_id=g.id` + - 新增返回字段:`t.group_id`、`g.name as group_name` +- 普通查询的 `ORDER BY` 改为 `ORDER BY g.name, k.updated_at DESC`,使同一群聊的文档聚在一起。 + +``` +位置:chatlog_fastAPI/routers/knowledge.py,第 18-33 行 +修改约 8 行 +``` + +--- + +#### 3. `chatlab-web/frontend/src/pages/KnowledgePage.jsx` + +**Bug 2 前端 — 文档列表按群聊分组展示** + +- 将原来平铺的 `docs.map(...)` 改为先按 `doc.group_name`(后端新增字段)分组,渲染时在每个群聊前加粘性标题头(灰色小号大写文字,背景 `var(--surface-2)`,`position: sticky`)。 +- `group_name` 为空时归入"未知群聊"分组。 + +``` +位置:KnowledgePage.jsx,第 96-114 行 +修改约 20 行,新增约 30 行 +``` + +--- + +#### 4. `chatlog_fastAPI/routers/topics.py` + +**Bug 3B 后端 — summarize 接口返回 task_id** + +- `POST /api/topics/{id}/summarize` 在调用 `run_summarize` 前,先向 `ai_tasks` 表插入一条类型为 `'summarize'`、状态为 `'running'` 的记录,并把 `task_id` 随响应返回:`{"ok": True, "task_id": task_id}`。 +- 前端可用 `getTask(task_id)` 轮询该记录的 `status`/`progress` 字段以展示进度。 + +``` +位置:chatlog_fastAPI/routers/topics.py,第 142-151 行 +修改约 10 行 +``` + +--- + +#### 5. `chatlog_fastAPI/services/summary_engine.py` + +**Bug 3B 后端 — run_summarize 接收 task_id,完成/出错时更新状态** + +- 函数签名新增可选参数 `task_id: int | None = None`。 +- 新增内部辅助函数 `_update_task(status, processed, total)`:打开独立数据库连接,UPDATE `ai_tasks` 记录的 `status` 和 `progress`。 +- 在以下关键节点调用 `_update_task`: + - 任务开始:`running, 0, 1` + - 无消息/无有效消息/LLM 失败:`error, 0, 1` + - 生成完成:`done, 1, 1` +- 新增 `import json`(用于序列化 progress 字段)。 + +``` +位置:chatlog_fastAPI/services/summary_engine.py,第 65-190 行 +修改约 8 行,新增约 20 行 +``` + +--- + +### 总结 + +| 文件 | 改动性质 | 解决的问题 | +|------|----------|-----------| +| `TopicsPage.jsx` | UI 替换 + 功能新增 | Bug1(可搜索下拉)+ Bug3A(初始化进度条)+ Bug3B(生成知识文档进度条)+ Bug4(移除按钮) | +| `KnowledgePage.jsx` | UI 重构 | Bug2(知识库按群聊分组展示) | +| `chatlog_fastAPI/routers/knowledge.py` | SQL 扩展 | Bug2(返回 group_name 字段) | +| `chatlog_fastAPI/routers/topics.py` | 功能新增 | Bug3B(summarize 接口返回 task_id) | +| `chatlog_fastAPI/services/summary_engine.py` | 功能新增 | Bug3B(运行时更新 ai_tasks 进度状态) | diff --git a/DISCLAIMER.md b/DISCLAIMER.md new file mode 100644 index 0000000..6ff12e7 --- /dev/null +++ b/DISCLAIMER.md @@ -0,0 +1,121 @@ +# Chatlog 免责声明 + +## 1. 定义 + +在本免责声明中,除非上下文另有说明,下列术语应具有以下含义: + +- **"本项目"或"Chatlog"**:指本开源软件项目,包括其源代码、可执行程序、文档及相关资源。 +- **"开发者"**:指本项目的创建者、维护者及代码贡献者。 +- **"用户"**:指下载、安装、使用或以任何方式接触本项目的个人或实体。 +- **"聊天数据"**:指通过各类即时通讯软件生成的对话内容及相关元数据。 +- **"合法授权"**:指根据适用法律法规,由数据所有者或数据主体明确授予的处理其聊天数据的权限。 +- **"第三方服务"**:指由非本项目开发者提供的外部服务,如大型语言模型(LLM) API 服务。 + +## 2. 使用目的与法律遵守 + +本项目仅供学习、研究和个人合法使用。用户须严格遵守所在国家/地区的法律法规使用本工具。任何违反法律法规、侵犯他人合法权益的行为,均与本项目及其开发者无关,相关法律责任由用户自行承担。 + +⚠️ **用户应自行了解并遵守当地有关数据访问、隐私保护、计算机安全和网络安全的法律法规。不同司法管辖区对数据处理有不同的法律要求,用户有责任确保其使用行为符合所有适用法规。** + +## 3. 授权范围与隐私保护 + +- 本工具仅限于处理用户自己合法拥有的聊天数据,或已获得数据所有者明确授权的数据。 +- 严禁将本工具用于未经授权获取、查看或分析他人聊天记录,或侵犯他人隐私权。 +- 用户应采取适当措施保护通过本工具获取和处理的聊天数据安全,包括但不限于加密存储、限制访问权限、定期删除不必要数据等。 +- 用户应确保其处理的聊天数据符合相关数据保护法规,包括但不限于获得必要的同意、保障数据主体权利、遵守数据最小化原则等。 + +## 4. 使用限制 + +- 本项目仅允许在合法授权情况下对聊天数据库进行备份与查看。 +- 未经明确授权,严禁将本项目用于访问、查看、分析或处理任何第三方聊天数据。 +- 使用第三方 LLM 服务时,用户应遵守相关服务提供商的服务条款和使用政策。 +- 用户不得规避本项目中的任何技术限制,或尝试反向工程、反编译或反汇编本项目,除非适用法律明确允许此类活动。 + +## 5. 技术风险声明 + +⚠️ **使用本项目存在以下技术风险,用户应充分了解并自行承担:** + +- 本工具需要访问聊天软件的数据库文件,可能因聊天软件版本更新导致功能失效或数据不兼容。 +- 在 macOS 系统上使用时,需要临时关闭 SIP 安全机制,这可能降低系统安全性,用户应了解相关风险并自行决定是否使用。 +- 本项目可能存在未知的技术缺陷或安全漏洞,可能导致数据损坏、丢失或泄露。 +- 使用本项目处理大量数据可能导致系统性能下降或资源占用过高。 +- 第三方依赖库或 API 的变更可能影响本项目的功能或安全性。 + +## 6. 禁止非法用途 + +严禁将本项目用于以下用途: + +- 从事任何形式的非法活动,包括但不限于未授权系统测试、网络渗透或其他违反法律法规的行为。 +- 监控、窃取或未经授权获取他人聊天记录或个人信息。 +- 将获取的数据用于骚扰、诈骗、敲诈、威胁或其他侵害他人合法权益的行为。 +- 规避任何安全措施或访问控制机制。 +- 传播虚假信息、仇恨言论或违反公序良俗的内容。 +- 侵犯任何第三方的知识产权、隐私权或其他合法权益。 + +**违反上述规定的,用户应自行承担全部法律责任,并赔偿因此给开发者或第三方造成的全部损失。** + +## 7. 第三方服务集成 + +- 用户将聊天数据与第三方 LLM 服务(如 OpenAI、Claude 等)结合使用时,应仔细阅读并遵守这些服务的使用条款、隐私政策和数据处理协议。 +- 用户应了解,向第三方服务传输数据可能导致数据离开用户控制范围,并受第三方服务条款约束。 +- 本项目开发者不对第三方服务的可用性、安全性、准确性或数据处理行为负责,用户应自行评估相关风险。 +- 用户应确保其向第三方服务传输数据的行为符合适用的数据保护法规和第三方服务条款。 + +## 8. 责任限制 + +**在法律允许的最大范围内:** + +- 本项目按"原样"和"可用"状态提供,不对功能的适用性、可靠性、准确性、完整性或及时性做任何明示或暗示的保证。 +- 开发者明确否认对适销性、特定用途适用性、不侵权以及任何其他明示或暗示的保证。 +- 本项目开发者和贡献者不对用户使用本工具的行为及后果承担任何法律责任。 +- 对于因使用本工具而可能导致的任何直接、间接、附带、特殊、惩罚性或后果性损失,包括但不限于数据丢失、业务中断、隐私泄露、声誉损害、利润损失、法律纠纷等,本项目开发者概不负责,即使开发者已被告知此类损失的可能性。 +- 在任何情况下,开发者对用户的全部责任累计不超过用户为获取本软件实际支付的金额(如为免费获取则为零)。 + +## 9. 知识产权声明 + +- 本项目基于 Apache-2.0 许可证开源,用户在使用、修改和分发时应严格遵守该许可证的所有条款。 +- 本项目的名称"Chatlog"、相关标识及商标权(如有)归开发者所有,未经明确授权,用户不得以任何方式使用这些标识进行商业活动。 +- 根据 Apache-2.0 许可证,用户可自由使用、修改和分发本项目代码,但须遵守许可证规定的归属声明等要求。 +- 用户对其修改版本自行承担全部责任,且不得以原项目名义发布,必须明确标明其为修改版本并与原项目区分。 +- 用户不得移除或更改本项目中的版权声明、商标或其他所有权声明。 + +## 10. 数据处理合规性 + +- 用户在使用本项目处理个人数据时,应遵守适用的数据保护法规,包括但不限于《中华人民共和国个人信息保护法》、《通用数据保护条例》(GDPR)等。 +- 用户应确保其具有处理相关数据的合法依据,如获得数据主体的明确同意。 +- 用户应实施适当的技术和组织措施,确保数据安全,防止未授权访问、意外丢失或泄露。 +- 在跨境传输数据时,用户应确保符合相关法律对数据出境的要求。 +- 用户应尊重数据主体权利,包括访问权、更正权、删除权等。 + +## 11. 免责声明接受 + +下载、安装、使用本项目,表示用户已阅读、理解并同意遵守本免责声明的所有条款。如不同意,请立即停止使用本工具并删除相关代码和程序。 + +**用户确认:** +- 已完整阅读并理解本免责声明的全部内容 +- 自愿接受本免责声明的全部条款 +- 具有完全民事行为能力,能够理解并承担使用本项目的风险和责任 +- 将遵守本免责声明中规定的所有义务和限制 + +## 12. 免责声明修改与通知 + +- 本免责声明可能根据项目发展和法律法规变化进行修改和调整,修改后的声明将在项目官方仓库页面公布。 +- 开发者没有义务个别通知用户免责声明的变更,用户应定期查阅最新版本。 +- 重大变更将通过项目仓库的 Release Notes 或 README 文件更新进行通知。 +- 在免责声明更新后继续使用本项目,即视为接受修改后的条款。 + +## 13. 法律适用与管辖 + +- 本免责声明受中华人民共和国法律管辖,并按其解释。 +- 任何与本免责声明有关的争议,应首先通过友好协商解决;协商不成的,提交至本项目开发者所在地有管辖权的人民法院诉讼解决。 +- 对于中国境外用户,如本免责声明与用户所在地强制性法律规定冲突,应以不违反该强制性规定的方式解释和适用本声明,但本声明的其余部分仍然有效。 + +## 14. 可分割性 + +如本免责声明中的任何条款被有管辖权的法院或其他权威机构认定为无效、不合法或不可执行,不影响其余条款的有效性和可执行性。无效条款应被视为从本声明中分割,并在法律允许的最大范围内由最接近原条款意图的有效条款替代。 + +## 15. 完整协议 + +本免责声明构成用户与开发者之间关于本项目使用的完整协议,取代先前或同时期关于本项目的所有口头或书面协议、提议和陈述。本声明的任何豁免、修改或补充均应以书面形式作出并经开发者签署方为有效。 + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6634c8c --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Alibaba Cloud + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..adbd2df --- /dev/null +++ b/README.md @@ -0,0 +1,114 @@ +# 微信知识库 - 使用说明 + +## 环境要求 + +| 软件 | 版本要求 | +|---|---| +| Windows | 10/11 64位 | +| 微信 | v4(推荐 4.1.5.30) | +| Node.js | 20+ LTS | +| Python | 3.10+ | + +--- + +## 一次性配置(首次使用) + +### 第一步:安装 Node.js + +前往 https://nodejs.org 下载 LTS 版本安装,安装时勾选 **"Add to PATH"**。 + +验证安装: +``` +node -v +``` + +### 第二步:安装 Python + +前往 https://www.python.org/downloads 下载 3.10 或更高版本。 + +安装时务必勾选 **"Add Python to PATH"**。 + +验证安装: +``` +python --version +``` + +### 第三步:安装 Python 依赖 + +打开命令提示符(CMD),进入 `chatlog_fastAPI` 目录执行: + +``` +cd 解压路径\get_wechat_me\chatlog_fastAPI +python -m pip install -r requirements.txt +``` + +## 启动方式 + +配置完成后,每次使用只需: + +**双击 `无痕启动控制台.vbs`** + +控制台窗口打开后,依次点击启动三个服务: + +1. **chatlog**(微信数据服务,端口 5030) +2. **FastAPI**(后端,端口 8000) +3. **前端**(端口 5173) + +三个服务全部启动后,点击"进入系统"即可使用。 + +--- + +## 注意事项 + +- 启动前请确保微信已登录 +- 若 chatlog 服务启动失败,尝试以**管理员身份**运行 `无痕启动控制台.vbs` +- 微信版本过新或过旧可能导致密钥提取失败,推荐使用 4.1.5.30 + +--- + +## 桌面版构建与代码签名 + +桌面版构建入口: + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1 +``` + +默认构建允许未签名安装包,适合本机测试。未签名包在客户电脑上可能触发 Windows SmartScreen 或杀毒软件提示,这是 Windows 对未知发布者程序的常见提示。 + +如已有 Windows 代码签名 PFX/P12 证书,可在构建时启用签名: + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1 ` + -Sign ` + -CertificateFile "D:\certs\ChatLab-CodeSigning.pfx" ` + -CertificatePassword "证书密码" ` + -PublisherName "证书中的发布者名称" ` + -ForceSign +``` + +也可以使用环境变量,避免把证书密码写进命令历史: + +```powershell +$env:CHATLAB_PFX_FILE = "D:\certs\ChatLab-CodeSigning.pfx" +$env:CHATLAB_PFX_PASSWORD = "证书密码" +$env:CHATLAB_CERT_PUBLISHER_NAME = "证书中的发布者名称" +$env:CHATLAB_FORCE_SIGN = "1" +powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1 +``` + +签名相关环境变量: + +| 变量 | 说明 | +|---|---| +| `CHATLAB_PFX_FILE` | PFX/P12 证书完整路径 | +| `CHATLAB_PFX_PASSWORD` | 证书密码 | +| `CHATLAB_CERT_PUBLISHER_NAME` | 可选,证书发布者名称 | +| `CHATLAB_TIMESTAMP_SERVER` | 可选,默认 `http://timestamp.digicert.com` | +| `CHATLAB_FORCE_SIGN` | 设为 `1` 时签名失败会中断构建 | + +安全要求: + +- 不要把 `.pfx`、`.p12`、`.pvk`、`.key`、`.cer`、`.crt` 或 `certs/` 放进项目或安装包资源目录。 +- 构建脚本会阻止证书、私钥、`.env`、`knowledge*.db`、`__pycache__` 进入发布资源。 +- 证书只通过本机路径参与签名,不会复制到客户安装包。 diff --git a/chatlab-web/README.md b/chatlab-web/README.md new file mode 100644 index 0000000..fc895eb --- /dev/null +++ b/chatlab-web/README.md @@ -0,0 +1,87 @@ +# ChatLab Web MVP + +> 设备售后微信知识库 — Web 端 MVP + +## 目录结构 + +``` +chatlab-web/ +├── frontend/ # React (Vite) 前端 +└── backend/ # Python FastAPI 后端 +``` + +> ⚠️ 此目录与 get_wechat 主项目完全独立,不修改任何现有文件 + +--- + +## 快速启动 + +### 1. 启动前端(含 Mock 数据,无需后端) + +```bash +cd chatlab-web/frontend +npm run dev +# 浏览器访问 http://localhost:5173 +``` + +### 2. 启动后端(接入 chatlog API) + +```bash +cd chatlab-web/backend +pip install -r requirements.txt +cp .env .env.local # 按需修改 CHATLOG_API 地址 +python main.py +# 后端运行在 http://localhost:8000 +``` + +### 3. 前端切换到真实后端 + +编辑 `frontend/src/api/index.js`,将 `USE_MOCK = true` 改为 `false`。 + +--- + +## 接口说明 + +### 存量查询(前端主动拉取) + +| 接口 | 方法 | 说明 | +|------|------|------| +| `/api/chatroom` | GET | 获取群聊列表 | +| `/api/v1/chatlog` | GET | 拉取存量聊天记录(分页) | +| `/api/session` | GET | 最近会话列表 | +| `/api/contact` | GET | 联系人搜索 | + +### 增量推送(Webhook → SSE) + +| 接口 | 方法 | 说明 | +|------|------|------| +| `/api/webhook` | POST | chatlog 推送新消息(配置到 chatlog) | +| `/api/sse/chatlog?talker=群ID` | GET (SSE) | 前端订阅实时推送 | +| `/api/health` | GET | 健康检查 | + +--- + +## chatlog API 对接 + +后端代理到 `http://127.0.0.1:5030`,参考接口: + +``` +GET /api/v1/chatlog?talker={群ID}&time={start,end}&limit={n}&offset={n}&format=json +POST {本后端 webhook URL} ← chatlog 配置 Webhook 推送地址 +``` + +具体入参出参待确认后更新 `backend/main.py` 中的代理逻辑。 + +--- + +## MVP 功能范围 + +- [x] 深色主题 UI + 完整设计系统 +- [x] 群聊列表 Sidebar +- [x] 聊天记录检索(时间范围 / 发送人多选 / 关键词) +- [x] 消息气泡展示(按天分组) +- [x] 关键词高亮 +- [x] Webhook 增量接收 + SSE 实时推送 +- [x] Mock 数据层(可一键切换真实 API) +- [ ] AI 话题分类(P0,待接入) +- [ ] 知识库(P0,待接入) diff --git a/chatlab-web/frontend/.gitignore b/chatlab-web/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/chatlab-web/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/chatlab-web/frontend/README.md b/chatlab-web/frontend/README.md new file mode 100644 index 0000000..a36934d --- /dev/null +++ b/chatlab-web/frontend/README.md @@ -0,0 +1,16 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/chatlab-web/frontend/eslint.config.js b/chatlab-web/frontend/eslint.config.js new file mode 100644 index 0000000..ea36dd3 --- /dev/null +++ b/chatlab-web/frontend/eslint.config.js @@ -0,0 +1,21 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{js,jsx}'], + extends: [ + js.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + parserOptions: { ecmaFeatures: { jsx: true } }, + }, + }, +]) diff --git a/chatlab-web/frontend/index.html b/chatlab-web/frontend/index.html new file mode 100644 index 0000000..3b27910 --- /dev/null +++ b/chatlab-web/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + ChatLab — 售后知识库 + + + +
+ + + diff --git a/chatlab-web/frontend/package-lock.json b/chatlab-web/frontend/package-lock.json new file mode 100644 index 0000000..33330e4 --- /dev/null +++ b/chatlab-web/frontend/package-lock.json @@ -0,0 +1,4470 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@tanstack/react-query": "^5.99.2", + "axios": "^1.15.2", + "dayjs": "^1.11.20", + "docx": "^9.6.1", + "lucide-react": "^1.8.0", + "react": "^19.2.5", + "react-dom": "^19.2.5", + "react-markdown": "^10.1.0", + "react-router-dom": "^7.14.2", + "remark-gfm": "^4.0.1" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.2.1", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.5.0", + "vite": "^8.0.10" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@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.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@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.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "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.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", + "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@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/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", + "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", + "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", + "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", + "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tanstack/query-core": { + "version": "5.99.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.99.2.tgz", + "integrity": "sha512-1HunU0bXVsR1ZJMZbcOPE6VtaBJxsW809RE9xPe4Gz7MlB0GWwQvuTPhMoEmQ/hIzFKJ/DWAuttIe7BOaWx0tA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.99.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.99.2.tgz", + "integrity": "sha512-vM91UEe45QUS9ED6OklsVL15i8qKcRqNwpWzPTVWvRPRSEgDudDgHpvyTjcdlwHcrKNa80T+xXYcchT2noPnZA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.99.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.7.0", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-25.7.0.tgz", + "integrity": "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", + "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.7" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", + "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.21.tgz", + "integrity": "sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/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/caniuse-lite": { + "version": "1.0.30001790", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz", + "integrity": "sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==", + "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/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/docx": { + "version": "9.6.1", + "resolved": "https://registry.npmmirror.com/docx/-/docx-9.6.1.tgz", + "integrity": "sha512-ZJja9/KBUuFC109sCMzovoq2GR2wCG/AuxivjA+OHj/q0TEgJIm3S7yrlUxIy3B+bV8YDj/BiHfWyrRFmyWpDQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^25.2.3", + "hash.js": "^1.1.7", + "jszip": "^3.10.1", + "nanoid": "^5.1.3", + "xml": "^1.0.1", + "xml-js": "^1.6.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/docx/node_modules/nanoid": { + "version": "5.1.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.11.tgz", + "integrity": "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/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/electron-to-chromium": { + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.1.tgz", + "integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.5.5", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/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/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", + "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/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-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/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/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "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/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "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/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/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.1", + "resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "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/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/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/lucide-react": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.8.0.tgz", + "integrity": "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "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/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.5" + } + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-router": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.2.tgz", + "integrity": "sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.2.tgz", + "integrity": "sha512-YZcM5ES8jJSM+KrJ9BdvHHqlnGTg5tH3sC5ChFRj4inosKctdyzBDhOyyHdGk597q2OT6NTrCA1OvB/YDwfekQ==", + "license": "MIT", + "dependencies": { + "react-router": "7.14.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "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/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", + "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.127.0", + "@rolldown/pluginutils": "1.0.0-rc.17" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-x64": "1.0.0-rc.17", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", + "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", + "dev": true, + "license": "MIT" + }, + "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/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "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/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/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/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "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/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/undici-types": { + "version": "7.21.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.21.0.tgz", + "integrity": "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==", + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.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/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", + "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.10", + "rolldown": "1.0.0-rc.17", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "license": "MIT" + }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmmirror.com/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "license": "MIT", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/chatlab-web/frontend/package.json b/chatlab-web/frontend/package.json new file mode 100644 index 0000000..0b12e3c --- /dev/null +++ b/chatlab-web/frontend/package.json @@ -0,0 +1,35 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/react-query": "^5.99.2", + "axios": "^1.15.2", + "dayjs": "^1.11.20", + "docx": "^9.6.1", + "lucide-react": "^1.8.0", + "react": "^19.2.5", + "react-dom": "^19.2.5", + "react-markdown": "^10.1.0", + "react-router-dom": "^7.14.2", + "remark-gfm": "^4.0.1" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.2.1", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.5.0", + "vite": "^8.0.10" + } +} diff --git a/chatlab-web/frontend/public/company-logo.jpg b/chatlab-web/frontend/public/company-logo.jpg new file mode 100644 index 0000000..c5ad946 Binary files /dev/null and b/chatlab-web/frontend/public/company-logo.jpg differ diff --git a/chatlab-web/frontend/public/favicon.svg b/chatlab-web/frontend/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/chatlab-web/frontend/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/chatlab-web/frontend/public/icons.svg b/chatlab-web/frontend/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/chatlab-web/frontend/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/chatlab-web/frontend/src/App.jsx b/chatlab-web/frontend/src/App.jsx new file mode 100644 index 0000000..9bdc8a1 --- /dev/null +++ b/chatlab-web/frontend/src/App.jsx @@ -0,0 +1,336 @@ +import { useState, useEffect, useMemo, useRef, useCallback } from 'react' +import { MessageSquare, BookOpen, Bot, Settings, Wifi, Users, Search } from 'lucide-react' +import dayjs from 'dayjs' +import 'dayjs/locale/zh-cn' +import relativeTime from 'dayjs/plugin/relativeTime' +import ChatlogPage from './pages/ChatlogPage' +import TopicsPage from './pages/TopicsPage' +import KnowledgePage from './pages/KnowledgePage' +import SettingsPage from './pages/SettingsPage' +import { getSessions } from './api' +import './index.css' + +dayjs.extend(relativeTime) +dayjs.locale('zh-cn') + +const NAV_ITEMS = [ + { id: 'chatlog', label: '聊天记录', icon: MessageSquare }, + { id: 'topics', label: 'AI 话题分析', icon: Bot }, + { id: 'knowledge', label: '报告库', icon: BookOpen }, + { id: 'settings', label: '设置', icon: Settings }, +] + +// ── 持久化缓存(localStorage,用于账号指纹检测) ────── +const CACHE_KEY = 'chatlab_sessions_v3' +const ACCOUNT_KEY = 'chatlab_account_fingerprint' + +function loadSessionCache() { + try { + const raw = localStorage.getItem(CACHE_KEY) + if (!raw) return [] + return JSON.parse(raw) + } catch { return [] } +} + +function saveSessionCache(sessions) { + try { + localStorage.setItem(CACHE_KEY, JSON.stringify(sessions)) + } catch {} +} + +// 账号指纹:取前3个会话 id 拼接,用于检测账号是否切换 +function calcFingerprint(sessions) { + return sessions.slice(0, 3).map(s => s.id).join('|') +} + +function loadAccountFingerprint() { + try { return localStorage.getItem(ACCOUNT_KEY) || '' } catch { return '' } +} + +function saveAccountFingerprint(fp) { + try { localStorage.setItem(ACCOUNT_KEY, fp) } catch {} +} + +export default function App() { + const [activeNav, setActiveNav] = useState('chatlog') + // sessions 初始为空数组,不从 localStorage 冷加载,避免旧账号数据闪现 + const [sessions, setSessions] = useState([]) + const [selectedRoom, setSelectedRoom] = useState(null) + const [toasts, setToasts] = useState([]) + const [loadError, setLoadError] = useState(null) + const [searchQuery, setSearchQuery] = useState('') + const [loading, setLoading] = useState(false) + const [, setTick] = useState(0) + + // ── 加载会话列表(含账号指纹检测,账号切换时清空旧缓存) ── + const loadSessions = useCallback((isPolling = false) => { + if (!isPolling) setLoading(true) + getSessions() + .then((res) => { + const list = res?.data || [] + list.sort((a, b) => b.lastTime - a.lastTime) + + // 账号指纹检测:若与上次不同说明账号已切换,清空旧数据 + const newFp = calcFingerprint(list) + const oldFp = loadAccountFingerprint() + if (oldFp && newFp && oldFp !== newFp) { + // 账号已切换:清空缓存、重置选中群 + localStorage.removeItem(CACHE_KEY) + setSelectedRoom(null) + } + saveAccountFingerprint(newFp) + + setSessions(list) + saveSessionCache(list) + // 首次加载(非轮询)且尚未选中群时,选中第一个 + if (!isPolling && list.length > 0 && !selectedRoom) setSelectedRoom(list[0]) + }) + .catch((e) => { + if (isPolling) return // 轮询失败静默处理 + const status = e?.response?.status + const detail = e?.response?.data?.detail || e?.response?.data?.error || '' + if (!e?.response) { + setLoadError('无法连接服务,请确认已启动 chatlog server(端口 5030)和 FastAPI(端口 8000)') + } else if (status >= 500) { + setLoadError(detail || `FastAPI 服务内部错误 (${status}),请检查后端日志`) + } else { + setLoadError(detail || `请求失败:${e.message || status || '未知错误'}`) + } + console.error('[App] getSessions failed', e) + }) + .finally(() => { if (!isPolling) setLoading(false) }) + }, []) // eslint-disable-line + + useEffect(() => { + loadSessions(false) + }, []) // eslint-disable-line + + // ── 每 30 秒轮询一次,检测账号是否切换 ── + useEffect(() => { + const pollId = setInterval(() => loadSessions(true), 30_000) + return () => clearInterval(pollId) + }, [loadSessions]) + + useEffect(() => { + const id = setInterval(() => setTick((t) => t + 1), 60_000) + return () => clearInterval(id) + }, []) + + // ── 显示列表:搜索过滤 ── + const displaySessions = useMemo(() => { + const q = searchQuery.trim().toLowerCase() + if (!q) return sessions + return sessions.filter(s => + s.name?.toLowerCase().includes(q) || + s.id?.toLowerCase().includes(q) + ) + }, [sessions, searchQuery]) + + const addToast = (message, type = 'success') => { + const id = Date.now() + setToasts((t) => [...t, { id, message, type }]) + setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 3000) + } + + // SSE 新消息到达时更新对应会话的 lastTime/lastContent + const handleNewMessage = useCallback((roomId, msg) => { + setSessions(prev => { + const next = prev.map(s => + s.id === roomId + ? { ...s, lastTime: msg.timestamp, lastContent: msg.content || '' } + : s + ) + next.sort((a, b) => b.lastTime - a.lastTime) + saveSessionCache(next) + return next + }) + }, []) + + return ( +
+ {/* ─── Sidebar ─────────────────────────────── */} + + + {/* ─── Main ─────────────────────────────────── */} +
+ {/* Topbar */} +
+
+
+ {selectedRoom ? selectedRoom.name : '聊天记录检索'} +
+ {selectedRoom && ( +
+ {selectedRoom.isGroup ? '微信群聊' : '私聊'} · {selectedRoom.id} +
+ )} +
+
+
+ + chatlog API: 127.0.0.1:5030 +
+
+
+ + {/* Page Content */} + {activeNav === 'chatlog' && ( + + )} + {activeNav === 'topics' && ( + + )} + {activeNav === 'knowledge' && ( + + )} + {activeNav === 'settings' && ( + + )} +
+ + {/* ─── Toasts ──────────────────────────────── */} +
+ {toasts.map((t) => ( +
+ {t.type === 'success' ? '✅' : '⚠️'} {t.message} +
+ ))} +
+
+ ) +} + +const ROOM_COLORS = ['#6366f1', '#8b5cf6', '#ec4899', '#f59e0b', '#10b981', '#3b82f6'] +function getRoomColor(id) { + if (!id) return ROOM_COLORS[0] + let hash = 0 + for (let c of id) hash = (hash * 31 + c.charCodeAt(0)) & 0xffffffff + return ROOM_COLORS[Math.abs(hash) % ROOM_COLORS.length] +} + +function truncate(str, maxLen) { + if (!str) return '' + const clean = str.replace(/\n/g, ' ') + return clean.length > maxLen ? clean.slice(0, maxLen) + '...' : clean +} diff --git a/chatlab-web/frontend/src/api/index.js b/chatlab-web/frontend/src/api/index.js new file mode 100644 index 0000000..d9cb1e0 --- /dev/null +++ b/chatlab-web/frontend/src/api/index.js @@ -0,0 +1,279 @@ +/** + * API 接口层 — 对接 chatlog_fastAPI 业务层(8000 端口,通过 vite proxy 转发) + * + * FastAPI 路由(routers/search.py): + * GET /api/search/chatrooms?keyword=&limit=&offset= + * GET /api/search/members?talker=&time= + * GET /api/search?talker=&time=&sender=&keyword=&page=&page_size= + * + * chatlog 底层字段说明(FastAPI 透传不做字段转换): + * 群聊 : name / nickName / remark / owner / users + * 消息 : seq / time(ISO) / sender / senderName / talker / talkerName / type / subType / content + * 成员 : userName / displayName / msgCount / lastSpeakTime + */ + +import axios from 'axios' +import dayjs from 'dayjs' + +const api = axios.create({ timeout: 30000 }) + +api.interceptors.response.use( + (res) => res.data, + (err) => { + console.error('[API Error]', err.response?.data || err.message) + return Promise.reject(err) + } +) + +// ───────────────────────────────────────────── +// 群聊列表 +// FastAPI: GET /api/search/chatrooms?keyword=&limit=&offset= +// 返回: { total, items: [ { Name, NickName, Remark, Owner, Users } ] } +// ───────────────────────────────────────────── +export async function getChatrooms(keyword = '') { + const raw = await api.get('/api/search/chatrooms', { params: { keyword, limit: 100, offset: 0 } }) + const items = Array.isArray(raw) ? raw : (raw.items || []) + const rooms = items.map((r) => ({ + id: r.name || r.Name, + name: r.nickName || r.NickName || r.remark || r.Remark || r.name || r.Name, + memberCount: (r.users || r.Users)?.length ?? 0, + platform: 'wechat', + })) + return { data: rooms } +} + +// ───────────────────────────────────────────── +// 会话列表(含最新消息预览和时间,来自微信原生 Session 表) +// FastAPI: GET /api/search/sessions?limit=500 +// 返回: [{ userName, nickName, remark, content, nTime, nOrder }] +// ───────────────────────────────────────────── +export async function getSessions(keyword = '') { + const raw = await api.get('/api/search/sessions', { params: { keyword, limit: 500 } }) + const items = Array.isArray(raw) ? raw : (raw.items || []) + const sessions = items.map((r) => ({ + id: r.userName, + name: r.nickName || r.remark || r.userName, + platform: 'wechat', + lastContent: r.content || '', + lastTime: r.nTime ? dayjs(r.nTime).unix() : 0, + nOrder: r.nOrder || 0, + isGroup: r.userName?.endsWith('@chatroom'), + })) + return { data: sessions.filter(s => s.isGroup) } +} + +// ───────────────────────────────────────────── +// 群成员列表(含发言统计) +// FastAPI: GET /api/search/members?talker=&time= +// 返回: { members: [{userName, displayName, msgCount, lastSpeakTime}], total } +// ───────────────────────────────────────────── +export async function getChatroomMembers(roomId) { + const raw = await api.get('/api/search/members', { params: { talker: roomId } }) + const members = (raw.members || []).map((m) => ({ + platformId: m.userName, + accountName: m.displayName || m.userName, + groupNickname: m.displayName, + })) + return { data: members } +} + +// ───────────────────────────────────────────── +// 存量聊天记录(核心接口) +// FastAPI: GET /api/search?talker=&time=&sender=&keyword=&page=&page_size= +// time 格式: "YYYY-MM-DD,YYYY-MM-DD"(逗号分隔) +// limit/offset → page/page_size(page 从 1 开始) +// 返回: { total, items: [ { Seq, Time, Sender, SenderName, Talker, TalkerName, Type, Content } ] } +// ───────────────────────────────────────────── +export async function getChatlog({ + talker, + startTime, + endTime, + senders = [], + keyword = '', + limit = 100, + offset = 0, +}) { + let time = '' + if (startTime && endTime) { + const fmt = (unix) => dayjs.unix(unix).format('YYYY-MM-DD') + time = `${fmt(startTime)},${fmt(endTime)}` + } + + const page = Math.floor(offset / limit) + 1 + const page_size = limit + + const params = { + talker, + page, + page_size, + ...(time ? { time } : {}), + ...(keyword ? { keyword } : {}), + ...(senders.length > 0 ? { sender: senders.join(',') } : {}), + } + + const raw = await api.get('/api/search', { params }) + const items = raw.items || [] + + const messages = items.map((m) => { + const isFile = Number(m.type) === 49 && Number(m.subType) === 6 + const fileMd5 = isFile ? (m.contents?.md5 || '') : '' + const fileName = isFile ? (m.contents?.title || m.contents?.fileName || m.contents?.filename || '') : '' + return { + id: String(m.seq), + sender: m.sender || '', + accountName: m.senderName || m.sender || '', + groupNickname: m.senderName || '', + timestamp: m.time ? dayjs(m.time).unix() : 0, + type: convertMsgType(m.type, m.subType), + content: m.content || '', + subType: m.subType, + talker: m.talker, + talkerName: m.talkerName, + // 媒体文件标识(chatlog contents 字段各类型 key 不同) + // 图片: md5/rawmd5 → chatlog 按 md5 查库 + // 视频: path(Windows 反斜杠需转成正斜杠让 handleMedia 走 findPath 分支) + // 语音: contents.voice = ServerID → /voice/{serverid} + // 文件: contents.md5 → /file/{md5} + // 表情包: contents.cdnurl = 外部 CDN 直链 + mediaKey: m.contents?.rawmd5 || m.contents?.md5 || m.contents?.path?.replace(/\\/g, '/') || '', + voiceKey: m.contents?.voice || '', // 语音专用 ServerID key + mediaMd5: m.contents?.md5 || '', + mediaPath: (m.contents?.path || '').replace(/\\/g, '/'), + emojiUrl: m.contents?.cdnurl || '', // 表情包 CDN 直链 + linkTitle: isFile ? '' : (m.contents?.title || ''), // 链接/公众号卡片 + linkDesc: isFile ? '' : (m.contents?.desc || ''), + linkUrl: isFile ? '' : (m.contents?.url || m.content || ''), + linkThumb: isFile ? '' : (m.contents?.thumbUrl || ''), + linkSource: isFile ? '' : (m.contents?.sourceName || ''), + quote: m.quote || null, + isFile, + fileName, + fileMd5, + fileUrl: fileMd5 ? `/api/files/${encodeURIComponent(fileMd5)}?filename=${encodeURIComponent(fileName || fileMd5)}` : '', + } + }) + + return { + data: { + messages, + total: raw.total ?? messages.length, + hasMore: (offset + limit) < (raw.total ?? 0), + }, + } +} + +// ───────────────────────────────────────────── +// Webhook 实时推送(SSE 方式) +// ───────────────────────────────────────────── +export function subscribeWebhook(talker, callback) { + const url = `/api/sse/chatlog?talker=${encodeURIComponent(talker)}` + let es + try { + es = new EventSource(url) + es.onmessage = (e) => { + try { + const raw = JSON.parse(e.data) + const isFile = Number(raw.type) === 49 && Number(raw.subType) === 6 + const fileMd5 = isFile ? (raw.contents?.md5 || '') : '' + const fileName = isFile ? (raw.contents?.title || raw.contents?.fileName || raw.contents?.filename || '') : '' + const msg = { + id: String(raw.seq || Date.now()), + sender: raw.sender || '', + accountName: raw.senderName || raw.sender || '', + groupNickname: raw.senderName || '', + timestamp: raw.time ? dayjs(raw.time).unix() : dayjs().unix(), + type: convertMsgType(raw.type, raw.subType), + content: raw.content || '', + subType: raw.subType, + talker: raw.talker, + talkerName: raw.talkerName, + mediaKey: raw.contents?.rawmd5 || raw.contents?.md5 || raw.contents?.path?.replace(/\\/g, '/') || '', + voiceKey: raw.contents?.voice || '', + mediaMd5: raw.contents?.md5 || '', + mediaPath: (raw.contents?.path || '').replace(/\\/g, '/'), + emojiUrl: raw.contents?.cdnurl || '', + linkTitle: isFile ? '' : (raw.contents?.title || ''), + linkDesc: isFile ? '' : (raw.contents?.desc || ''), + linkUrl: isFile ? '' : (raw.contents?.url || raw.content || ''), + linkThumb: isFile ? '' : (raw.contents?.thumbUrl || ''), + linkSource: isFile ? '' : (raw.contents?.sourceName || ''), + quote: raw.quote || null, + isFile, + fileName, + fileMd5, + fileUrl: fileMd5 ? `/api/files/${encodeURIComponent(fileMd5)}?filename=${encodeURIComponent(fileName || fileMd5)}` : '', + } + callback(msg) + } catch { + // 忽略解析错误 + } + } + es.onerror = () => { + console.warn('[SSE] 连接失败或断开,Webhook 实时推送不可用') + } + } catch { + console.warn('[SSE] EventSource 创建失败') + } + + return () => { + es?.close() + } +} + +export function triggerMockWebhook() { + console.info('[Webhook] 真实模式下无法模拟推送,请通过 chatlog 发送新消息触发') +} + +// ───────────────────────────────────────────── +// Groups(监控群组管理) +// ───────────────────────────────────────────── +export const getGroups = () => api.get('/api/groups') +export const createGroup = (talker, name) => api.post('/api/groups', { talker, name }) +export const patchGroup = (groupId, body) => api.patch(`/api/groups/${groupId}`, body) +export const initGroup = (groupId, { startTime, endTime }) => + api.post(`/api/groups/${groupId}/init`, { start_time: startTime, end_time: endTime }) +export const getGroupTask = (groupId) => api.get(`/api/groups/${groupId}/task`) +export const deleteGroup = (groupId) => api.delete(`/api/groups/${groupId}`) + +// ───────────────────────────────────────────── +// Topics(AI 话题) +// ───────────────────────────────────────────── +export const getTopics = (params) => api.get('/api/topics', { params }) +export const getTopic = (id) => api.get(`/api/topics/${id}`) +export const createTopic = (group_id, title) => api.post('/api/topics', { group_id, title }) +export const patchTopic = (id, body) => api.patch(`/api/topics/${id}`, body) +export const deleteTopic = (id) => api.delete(`/api/topics/${id}`) +export const summarizeTopic = (id) => api.post(`/api/topics/${id}/summarize`) +export const addTopicMessage = (id, msg_seq, talker) => api.post(`/api/topics/${id}/messages`, { msg_seq, talker }) +export const removeTopicMessage = (id, seq) => api.delete(`/api/topics/${id}/messages/${seq}`) + +// ───────────────────────────────────────────── +// Knowledge(知识库) +// ───────────────────────────────────────────── +export const getKnowledge = (keyword) => api.get('/api/knowledge', { params: keyword ? { keyword } : {} }) +export const getKnowledgeDoc = (id) => api.get(`/api/knowledge/${id}`) +export const patchKnowledge = (id, content) => api.patch(`/api/knowledge/${id}`, { content }) + +// ───────────────────────────────────────────── +// Tasks +// ───────────────────────────────────────────── +export const getTask = (id) => api.get(`/api/tasks/${id}`) + +// ───────────────────────────────────────────── +// 消息类型映射 +// chatlog Type: 1=文字 3=图片 34=语音 43=视频 49=分享 10000=系统 +// ───────────────────────────────────────────── +function convertMsgType(rawType, subType) { + if (Number(rawType) === 49 && Number(subType) === 62) return 82 // 拍了拍 + const map = { + 1: 0, // 文字 + 3: 1, // 图片 + 34: 2, // 语音 + 43: 3, // 视频 + 47: 5, // 表情包 + 49: 7, // 分享/链接/文件 + 10000: 80, // 系统消息 + 10002: 81, // 撤回 + } + return map[rawType] ?? 99 +} diff --git a/chatlab-web/frontend/src/api/mock.js b/chatlab-web/frontend/src/api/mock.js new file mode 100644 index 0000000..0c4b7e3 --- /dev/null +++ b/chatlab-web/frontend/src/api/mock.js @@ -0,0 +1,119 @@ +/** + * Mock 数据层 + * MVP 阶段使用此文件模拟后端 API,等真实接口文档确认后替换 api/client.js 即可 + */ + +import dayjs from 'dayjs' + +// ── Mock 群聊列表 ────────────────────────────── +export const MOCK_CHATROOMS = [ + { id: 'room_001', name: '设备售后技术群', memberCount: 47, platform: 'wechat' }, + { id: 'room_002', name: '弯管机项目组', memberCount: 12, platform: 'wechat' }, + { id: 'room_003', name: '华南区售后', memberCount: 23, platform: 'wechat' }, + { id: 'room_004', name: '客服协调群', memberCount: 8, platform: 'wechat' }, +] + +// ── Mock 成员列表 ────────────────────────────── +export const MOCK_MEMBERS = { + room_001: [ + { platformId: 'u001', accountName: '张三', groupNickname: '调机师傅-张三' }, + { platformId: 'u002', accountName: '李四', groupNickname: '售后一组' }, + { platformId: 'u003', accountName: '王五', groupNickname: '王五' }, + { platformId: 'u004', accountName: '赵六', groupNickname: '技术总监' }, + { platformId: 'u005', accountName: '孙七', groupNickname: '孙工' }, + { platformId: 'u006', accountName: '周八', groupNickname: '周老板' }, + { platformId: 'me', accountName: '我', groupNickname: '(本机账号)' }, + ], + room_002: [ + { platformId: 'u001', accountName: '张三', groupNickname: '张三' }, + { platformId: 'u004', accountName: '赵六', groupNickname: '赵工' }, + { platformId: 'me', accountName: '我', groupNickname: '我' }, + ], + room_003: [ + { platformId: 'u002', accountName: '李四', groupNickname: '华南李四' }, + { platformId: 'u005', accountName: '孙七', groupNickname: '孙七' }, + { platformId: 'me', accountName: '我', groupNickname: '我' }, + ], + room_004: [ + { platformId: 'u006', accountName: '周八', groupNickname: '周总' }, + { platformId: 'me', accountName: '我', groupNickname: '我' }, + ], +} + +// ── Mock 消息生成 ────────────────────────────── +const now = dayjs() + +function makeMsg(sender, accountName, groupNickname, minutesAgo, content, type = 0) { + return { + id: `${sender}_${minutesAgo}`, + sender, + accountName, + groupNickname, + timestamp: now.subtract(minutesAgo, 'minute').unix(), + type, + content, + } +} + +export const MOCK_MESSAGES = { + room_001: [ + makeMsg('u001', '张三', '调机师傅-张三', 180, '那台弯管机又报警了,客户那边急着要货'), + makeMsg('u002', '李四', '售后一组', 178, '我这边也遇到了,注塑机模具对不上,顶针位置偏了'), + makeMsg('u001', '张三', '调机师傅-张三', 177, '报的是什么错误码?'), + makeMsg('u003', '王五', '王五', 176, '@李四 你先检查一下导柱有没有松动'), + makeMsg('u001', '张三', '调机师傅-张三', 174, 'E-1023,伺服过载报警'), + makeMsg('u002', '李四', '售后一组', 173, '导柱没问题啊,刚紧过'), + makeMsg('u003', '王五', '王五', 171, '那看看顶针有没有弯,之前有台机子就是这样'), + makeMsg('u004', '赵六', '技术总监', 168, '@张三 E-1023 是伺服过载,你先检查一下电机温度,再看看机械负载有没有异常'), + makeMsg('u001', '张三', '调机师傅-张三', 165, '温度正常,36度,我再看看机械那边'), + makeMsg('u004', '赵六', '技术总监', 162, '重点检查导轨润滑,这个型号的机器用的是23号导轨油,很多现场都缺油'), + makeMsg('u001', '张三', '调机师傅-张三', 158, '找到了!导轨这段完全干了,润滑油嘴堵了,加完油重启好了'), + makeMsg('u004', '赵六', '技术总监', 155, '好,这个问题做个记录。E-1023 80%是润滑问题,以后优先检查这里'), + makeMsg('u002', '李四', '售后一组', 150, '顶针换了,位置好了,谢谢王五'), + makeMsg('u003', '王五', '王五', 148, '好的好的,这个顶针弯了之后看起来不明显,要拿千分表测'), + makeMsg('u005', '孙七', '孙工', 120, '今天那个广州客户发来消息说液压系统压力不够,标准是16MPa,实测只有11MPa'), + makeMsg('u004', '赵六', '技术总监', 118, '检查溢流阀,旋钮可能被碰到调小了。调回去之前先确认液压油液位'), + makeMsg('u005', '孙七', '孙工', 115, '液压油液位正常,溢流阀找到了,调回16MPa,好了'), + makeMsg('u006', '周八', '周老板', 90, '大家注意,下周一华东区有个验厂,需要两个技术人员配合,谁有空?'), + makeMsg('u001', '张三', '调机师傅-张三', 88, '我可以,周一下午没安排'), + makeMsg('u004', '赵六', '技术总监', 85, '我跟张三去,先联系对方工厂准备材料'), + makeMsg('me', '我', '(本机账号)', 60, '刚查了下,那个广州客户的机器是定制款,液压系统压力设定是18MPa不是16MPa,是客户特殊要求的'), + makeMsg('u004', '赵六', '技术总监', 58, '对的,这个机器台账里有注记。要存到知识库里'), + makeMsg('u002', '李四', '售后一组', 30, '收到,我来整理一下今天几个问题的解决记录'), + makeMsg('u001', '张三', '调机师傅-张三', 15, '好,我补充一下弯管机那台的细节'), + makeMsg('me', '我', '(本机账号)', 5, '刚又来一个新报警,WG-50CNC 出现 F-2055,有人知道这个码吗?'), + ], + room_002: [ + makeMsg('u001', '张三', '张三', 300, 'WG-38CNC 那台调好了,弯管角度偏差在±0.3度以内'), + makeMsg('u004', '赵六', '赵工', 295, '好,符合客户要求。出货前再跑一遍程序确认'), + makeMsg('u001', '张三', '张三', 290, '已经跑了,没问题了'), + makeMsg('me', '我', '我', 60, '客户那边反馈说回弹补偿不够,120度弯出来实际是122度'), + makeMsg('u004', '赵六', '赵工', 55, '增加2度补偿试试,在参数页面改 K02 参数'), + makeMsg('me', '我', '我', 50, '改完了,现在是121度还差一点'), + makeMsg('u004', '赵六', '赵工', 45, '再加0.5度,如果还不够这批料可能弹性模量偏高,要换料'), + ], + room_003: [ + makeMsg('u002', '李四', '华南李四', 240, '深圳那个客户要下周来验收,设备已经调好了'), + makeMsg('u005', '孙七', '孙七', 235, '验收要准备什么材料?'), + makeMsg('u002', '李四', '华南李四', 230, '合格证、操作手册、维修手册、参数表,还有现场演示视频'), + makeMsg('me', '我', '我', 45, '广州那个液压问题解决了吗?'), + makeMsg('u002', '李四', '华南李四', 40, '解决了,溢流阀调回来就好了。那个机器是定制款压力18MPa'), + ], + room_004: [ + makeMsg('u006', '周八', '周总', 180, '下周一验厂,张三和赵工去,行程确认一下'), + makeMsg('me', '我', '我', 175, '好的,已经记下来了'), + makeMsg('u006', '周八', '周总', 50, '还有季度报告,这周五之前交'), + makeMsg('me', '我', '我', 45, '收到,我来汇总'), + ], +} + +// ── Webhook 增量消息池 ───────────────────────── +// 模拟 webhook 可能推入的增量消息 +export const MOCK_WEBHOOK_MESSAGES = { + room_001: [ + { sender: 'u003', accountName: '王五', groupNickname: '王五', content: '这个需求挺有意思的,F-2055 我查过,是刀具磨损检测报警' }, + { sender: 'u004', accountName: '赵六', groupNickname: '技术总监', content: 'F-2055 对,检查一下刀具刃口,如果磨损超标就换刀' }, + { sender: 'u001', accountName: '张三', groupNickname: '调机师傅-张三', content: '换完刀清一下报警就好了,这个很常见' }, + { sender: 'u005', accountName: '孙七', groupNickname: '孙工', content: '好的,我来处理' }, + ], +} diff --git a/chatlab-web/frontend/src/assets/hero.png b/chatlab-web/frontend/src/assets/hero.png new file mode 100644 index 0000000..02251f4 Binary files /dev/null and b/chatlab-web/frontend/src/assets/hero.png differ diff --git a/chatlab-web/frontend/src/assets/vite.svg b/chatlab-web/frontend/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/chatlab-web/frontend/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/chatlab-web/frontend/src/components/AISummaryPanel.jsx b/chatlab-web/frontend/src/components/AISummaryPanel.jsx new file mode 100644 index 0000000..69dde3a --- /dev/null +++ b/chatlab-web/frontend/src/components/AISummaryPanel.jsx @@ -0,0 +1,337 @@ +import { useState, useRef, useEffect } from 'react' +import { Sparkles, X, Copy, Check, Loader, Download } from 'lucide-react' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' + +const MEDIA_TYPE_MAP = { 1: 'image', 2: 'voice', 3: 'video' } + +function quoteContext(msg) { + if (!msg.quote?.content) return '' + const sender = msg.quote.sender_name || msg.quote.sender || '未知' + const seq = msg.quote.seq ? ` seq=${msg.quote.seq}` : '' + return `;[引用消息${seq}] ${sender}: ${msg.quote.content}` +} + +// 调用 chatlog 内置 AI 解析单条媒体消息,返回文字描述,失败则返回 null +async function parseOneMedia(msg) { + const aiType = MEDIA_TYPE_MAP[msg.type] + // 语音用 voiceKey(ServerID),图片/视频用 mediaKey(md5) + const key = msg.type === 2 + ? (msg.voiceKey || msg.mediaKey || '') + : (msg.mediaKey || '') + if (!aiType || !key) return null + try { + const res = await fetch('/api/ai/parse', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ type: aiType, key }), + }) + if (!res.ok) return null + const data = await res.json() + return data.text || data.result || null + } catch { + return null + } +} + +// 把 messages 列表转成发给 AI 的纯文本(含媒体解析结果) +async function buildContext(messages, onProgress) { + const lines = [] + let mediaCount = 0 + const mediaMessages = messages.filter(m => { + if (!MEDIA_TYPE_MAP[m.type]) return false + return m.type === 2 ? (m.voiceKey || m.mediaKey) : m.mediaKey + }) + + // 先解析所有媒体(并发,最多 5 个同时) + const mediaResults = new Map() + const chunks = [] + for (let i = 0; i < mediaMessages.length; i += 5) chunks.push(mediaMessages.slice(i, i + 5)) + for (const chunk of chunks) { + const results = await Promise.all(chunk.map(m => parseOneMedia(m))) + chunk.forEach((m, i) => { if (results[i]) mediaResults.set(m.id, results[i]) }) + mediaCount += chunk.length + onProgress(Math.min(90, Math.round((mediaCount / Math.max(mediaMessages.length, 1)) * 80))) + } + + // 组装上下文文本 + for (const m of messages) { + const sender = m.accountName || m.sender || '未知' + const typeLabel = { 0: '', 1: '[图片]', 2: '[语音]', 3: '[视频]', 5: '[表情]', 7: '[链接]', 80: '[系统]', 81: '[撤回]' }[m.type] || `[类型${m.type}]` + const mediaDesc = mediaResults.get(m.id) + const quoteText = quoteContext(m) + + if (m.type === 0 && m.content) { + lines.push(`${sender}: ${m.content}${quoteText}`) + } else if (mediaDesc) { + lines.push(`${sender} ${typeLabel}: ${mediaDesc}${quoteText}`) + } else if (m.content && m.type !== 0) { + lines.push(`${sender} ${typeLabel}: ${m.content}${quoteText}`) + } else { + lines.push(`${sender} ${typeLabel}${quoteText}`) + } + } + + return { context: lines.join('\n'), parsedMedia: mediaResults.size } +} + +/** + * AI 总结面板 + * 先解析媒体(图片/语音/视频),再把全部内容一起送给 AI 总结 + */ +export default function AISummaryPanel({ messages, roomName, onClose }) { + const [phase, setPhase] = useState('idle') // idle | parsing | loading | streaming | done | error + const [parseProgress, setParseProgress] = useState(0) + const [parsedMedia, setParsedMedia] = useState(0) + const [content, setContent] = useState('') + const [copied, setCopied] = useState(false) + const abortRef = useRef(null) + + useEffect(() => { + handleGenerate() + return () => abortRef.current?.abort() + }, []) // eslint-disable-line + + const handleGenerate = async () => { + setContent('') + setParseProgress(0) + setParsedMedia(0) + + const controller = new AbortController() + abortRef.current = controller + + try { + // ── 第一步:解析媒体 ── + const mediaMessages = messages.filter(m => { + if (!MEDIA_TYPE_MAP[m.type]) return false + return m.type === 2 ? (m.voiceKey || m.mediaKey) : m.mediaKey + }) + if (mediaMessages.length > 0) { + setPhase('parsing') + const { context, parsedMedia: count } = await buildContext(messages, setParseProgress) + setParsedMedia(count) + setParseProgress(100) + // 稍等一下让用户看到 100% + await new Promise(r => setTimeout(r, 300)) + await streamSummary(context, roomName, controller, setPhase, setContent) + } else { + // 没有媒体,直接总结文本 + const textContext = messages + .filter(m => (m.type === 0 && m.content) || m.quote?.content) + .map(m => `${m.accountName || m.sender}: ${m.content || ''}${quoteContext(m)}`) + .join('\n') + await streamSummary(textContext, roomName, controller, setPhase, setContent) + } + } catch (e) { + if (e.name === 'AbortError') return + setContent(`**生成失败**: ${e.message}`) + setPhase('error') + } + } + + const handleCopy = () => { + navigator.clipboard.writeText(content) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + + const mediaTotal = messages.filter(m => { + if (!MEDIA_TYPE_MAP[m.type]) return false + return m.type === 2 ? (m.voiceKey || m.mediaKey) : m.mediaKey + }).length + + return ( +
+
+ {/* Header */} +
+ + + AI 知识总结 + + + {messages.length} 条消息 · {roomName} + {mediaTotal > 0 && ` · ${mediaTotal} 条媒体`} + + +
+ {phase === 'done' && ( + + )} + {content && ( + + )} + {content && ( + + )} + +
+
+ + {/* Content */} +
+ {/* 媒体解析进度 */} + {phase === 'parsing' && ( +
+
+
+ 正在解析媒体内容(图片 / 语音 / 视频)... + + {parseProgress}% + +
+
+
+
+
+ 共 {mediaTotal} 条媒体,已解析 {parsedMedia} 条 +
+
+ )} + + {phase === 'loading' && ( +
+
+ 正在连接 AI 服务... +
+ )} + + {(phase === 'streaming' || phase === 'done' || phase === 'error') && ( +
+ + {content || ' '} + + {phase === 'streaming' && ( + + )} +
+ )} +
+ + {/* Footer */} +
+ {phase === 'parsing' && <> 解析媒体中({parseProgress}%)...} + {phase === 'loading' && <> 连接中...} + {phase === 'streaming' && <> 生成中...} + {phase === 'done' && <> 生成完成{parsedMedia > 0 && `(含 ${parsedMedia} 条媒体内容)`}} + {phase === 'error' && <> 生成失败} + chatlog AI · 媒体感知总结 +
+
+ + {/* Markdown 样式 */} + +
+ ) +} + +// 流式请求 AI 总结接口 +async function streamSummary(context, roomName, controller, setPhase, setContent) { + setPhase('loading') + const resp = await fetch('/api/ai/summarize/stream', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ context, room_name: roomName }), + signal: controller.signal, + }) + + if (!resp.ok) { + const err = await resp.json().catch(() => ({})) + throw new Error(err.detail || `HTTP ${resp.status}`) + } + + setPhase('streaming') + const reader = resp.body.getReader() + const decoder = new TextDecoder() + let buffer = '' + + while (true) { + const { done, value } = await reader.read() + if (done) break + buffer += decoder.decode(value, { stream: true }) + const lines = buffer.split('\n') + buffer = lines.pop() + for (const line of lines) { + if (!line.startsWith('data: ')) continue + try { + const json = JSON.parse(line.slice(6)) + if (json.done) { setPhase('done'); return } + else if (json.error) throw new Error(json.error) + else if (json.delta) setContent(prev => prev + json.delta) + } catch (e) { + if (e.message !== 'Unexpected end of JSON input') throw e + } + } + } + setPhase('done') +} diff --git a/chatlab-web/frontend/src/components/MemberSelector.jsx b/chatlab-web/frontend/src/components/MemberSelector.jsx new file mode 100644 index 0000000..948234c --- /dev/null +++ b/chatlab-web/frontend/src/components/MemberSelector.jsx @@ -0,0 +1,131 @@ +import { useState, useRef, useEffect } from 'react' +import { Users, Check, X } from 'lucide-react' + +/** + * 多选成员选择器 + * 支持搜索、已选 Tag 展示、点击外部关闭 + */ +export default function MemberSelector({ members = [], selected = [], onChange }) { + const [open, setOpen] = useState(false) + const [search, setSearch] = useState('') + const ref = useRef(null) + + // 点击外部关闭 + useEffect(() => { + function handler(e) { + if (ref.current && !ref.current.contains(e.target)) setOpen(false) + } + document.addEventListener('mousedown', handler) + return () => document.removeEventListener('mousedown', handler) + }, []) + + const filtered = members.filter((m) => { + if (!search) return true + return ( + m.accountName?.includes(search) || + m.groupNickname?.includes(search) || + m.platformId?.includes(search) + ) + }) + + const toggle = (id) => { + if (selected.includes(id)) { + onChange(selected.filter((s) => s !== id)) + } else { + onChange([...selected, id]) + } + } + + const getMember = (id) => members.find((m) => m.platformId === id) + + return ( +
+
setOpen(!open)} + id="member-selector-trigger" + > + + {selected.length === 0 ? ( + 全部成员 + ) : ( + 已选 {selected.length} 人 + )} + +
+ + {open && ( +
+ {/* 搜索框 */} +
+ setSearch(e.target.value)} + autoFocus + /> +
+ + {/* 列表 */} +
+ {filtered.length === 0 && ( +
+ 无匹配成员 +
+ )} + {filtered.map((m) => { + const checked = selected.includes(m.platformId) + return ( +
toggle(m.platformId)} + > +
+ {checked && } +
+
+
{m.accountName}
+ {m.groupNickname && m.groupNickname !== m.accountName && ( +
{m.groupNickname}
+ )} +
+
+ ) + })} +
+ + {/* 已选 + 清空 */} +
+
+ {selected.length === 0 && ( + 未选择(显示全部) + )} + {selected.map((id) => { + const m = getMember(id) + return ( +
+ {m?.accountName || id} + { e.stopPropagation(); toggle(id) }}> + + +
+ ) + })} +
+ {selected.length > 0 && ( + + )} +
+
+ )} +
+ ) +} diff --git a/chatlab-web/frontend/src/components/MessageBubble.jsx b/chatlab-web/frontend/src/components/MessageBubble.jsx new file mode 100644 index 0000000..a6c60a9 --- /dev/null +++ b/chatlab-web/frontend/src/components/MessageBubble.jsx @@ -0,0 +1,696 @@ +import { useState, useEffect } from 'react' +import dayjs from 'dayjs' +import { Volume2, Video, FileText, Link, AlertCircle, Type, Play, Download, ExternalLink, Reply } from 'lucide-react' + +// key 可能是路径(含 /)或 md5,只编码各路径段,保留斜杠让 Gin *key 路由正确匹配 +const avatarCache = new Map() + +function fetchAvatar(wxid) { + if (avatarCache.has(wxid)) return avatarCache.get(wxid) + const p = fetch(`/api/search/avatar?wxid=${encodeURIComponent(wxid)}`) + .then(r => r.json()).then(d => d.url || '').catch(() => '') + avatarCache.set(wxid, p) + return p +} + +function mediaUrl(type, key) { + if (!key) return '' + return `/${type}/${key.split('/').map(p => encodeURIComponent(p)).join('/')}` +} + +// 调用 chatlog 内置 AI 解析(语音→文字,图片/视频→描述) +async function aiParse(type, key) { + const typeMap = { 1: 'image', 2: 'voice', 3: 'video' } + const aiType = typeMap[type] + if (!aiType || !key) throw new Error('参数不完整') + const res = await fetch('/api/ai/parse', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ type: aiType, key }), + }) + if (!res.ok) { + const err = await res.json().catch(() => ({})) + const detail = err.detail + if (detail && typeof detail === 'object') { + const e = new Error(detail.message || err.message || `HTTP ${res.status}`) + e.diagnostics = detail.diagnostics + throw e + } + throw new Error(err.message || detail || `HTTP ${res.status}`) + } + return res.json() // { text: "..." } +} + +function Avatar({ wxid, displayName, color }) { + const [imgUrl, setImgUrl] = useState(null) + const initials = displayName?.slice(0, 2) || wxid?.slice(-2) || '??' + useEffect(() => { + if (!wxid || wxid === 'me') return + fetchAvatar(wxid).then(url => { if (url) setImgUrl(url) }) + }, [wxid]) + return ( +
+ {initials} + {imgUrl && ( + setImgUrl(null)} + style={{ + position: 'absolute', + top: 0, left: 0, width: '100%', height: '100%', + objectFit: 'cover', + borderRadius: 'inherit', + }} + alt="" + /> + )} +
+ ) +} + +export default function MessageBubble({ msg, keyword = '', isNew = false }) { + const isMine = msg.sender === 'me' + const time = dayjs.unix(msg.timestamp).format('HH:mm') + const displayName = msg.groupNickname || msg.accountName + const initials = displayName?.slice(0, 2) || '??' + + function highlight(text) { + if (!keyword || !text) return text + const parts = text.split(new RegExp(`(${keyword})`, 'gi')) + return parts.map((part, i) => + part.toLowerCase() === keyword.toLowerCase() + ? {part} + : part + ) + } + + if (msg.type === 82) { + return ( +
+ + {msg.content || '[拍了拍]'} + +
+ ) + } + + return ( +
+ +
+
+ {msg.accountName} + {msg.groupNickname && msg.groupNickname !== msg.accountName && ( + ({msg.groupNickname}) + )} + {time} + {isNew && 新消息} +
+
+ +
+
+
+ ) +} + +function MsgContent({ msg, highlight }) { + // 图片/视频用 md5(chatlog 按 md5 查库),语音用 voiceKey(ServerID) + const mediaKey = msg.mediaKey || '' + const voiceKey = msg.voiceKey || '' + const emojiUrl = msg.emojiUrl || '' + const withQuote = (body) => ( + <> + + {body} + + ) + + switch (msg.type) { + case 0: + return withQuote({highlight(msg.content)}) + + case 1: + return withQuote() + + case 2: + return withQuote() + + case 3: + return withQuote() + + case 5: + return withQuote(emojiUrl + ? 表情包 + : mediaKey + ? + : ) + + case 7: + if (msg.isFile || msg.subType === 6) return withQuote() + return withQuote() + + case 80: + return ( + + {msg.content || '[系统消息]'} + + ) + + case 81: + return 撤回了一条消息 + + case 82: + return ( + + {msg.content || '[拍了拍]'} + + ) + + default: + return withQuote(} label={getTypeName(msg.type)} />) + } +} + +function QuoteBlock({ quote }) { + if (!quote?.content) return null + const sender = quote.sender_name || quote.sender || '引用消息' + return ( +
+
+ + {sender} +
+
{quote.content}
+
+ ) +} + +// ── 文件 ────────────────────────────────────── +function FileMsg({ msg }) { + const [error, setError] = useState('') + const fileName = msg.fileName || msg.linkTitle || msg.content?.replace(/^\[文件\|(.+)\]$/, '$1') || '文件' + const fileMd5 = msg.fileMd5 || msg.mediaMd5 || msg.mediaKey || '' + const fileUrl = msg.fileUrl || (fileMd5 ? `/api/files/${encodeURIComponent(fileMd5)}?filename=${encodeURIComponent(fileName || fileMd5)}` : '') + const ext = getFileExt(fileName) + + const openFile = () => { + setError('') + if (!fileUrl) { + setError('缺少文件标识,无法打开原文件') + return + } + const a = document.createElement('a') + a.href = fileUrl + a.download = fileName + a.rel = 'noopener noreferrer' + document.body.appendChild(a) + a.click() + a.remove() + } + + return ( +
+
+
+ + + {ext || 'FILE'} + +
+
+
+ {fileName} +
+
+ {fileMd5 ? `md5: ${fileMd5.slice(0, 10)}...` : '缺少文件标识'} +
+
+
+
+ + {error || (fileUrl ? '原文件' : '不可打开')} + + +
+
+ ) +} + +// ── 图片 ────────────────────────────────────── +function ImageMsg({ msgKey, msgType }) { + const [errored, setErrored] = useState(false) + const [enlarged, setEnlarged] = useState(false) + const [aiText, setAiText] = useState('') + const [aiLoading, setAiLoading] = useState(false) + const [aiError, setAiError] = useState('') + + if (!msgKey) return + + const url = mediaUrl('image', msgKey) + const thumbUrl = url + '?thumb=1' + + const handleAiDesc = async () => { + setAiLoading(true) + setAiError('') + try { + const result = await aiParse(msgType, msgKey) + setAiText(result.text || result.result || JSON.stringify(result)) + } catch (e) { + setAiError(e.message) + } finally { + setAiLoading(false) + } + } + + return ( +
+ {errored ? ( + // 加载失败时仍保留点击 AI 描述的入口 +
+ 🖼️ 图片(文件未找到) + +
+ ) : ( +
+ 图片 setErrored(true)} + onClick={() => setEnlarged(true)} + /> + {/* AI 描述按钮浮在图片右下角 */} +
+ +
+
+ )} + + {/* AI 描述结果 */} + {(aiText || aiError) && ( +
+ {aiError ? `AI 识别失败:${aiError}` : aiText} +
+ )} + + {/* 放大预览 */} + {enlarged && ( +
setEnlarged(false)} + style={{ + position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.88)', + display: 'flex', alignItems: 'center', justifyContent: 'center', + zIndex: 9999, cursor: 'zoom-out', + }} + > + 原图 e.stopPropagation()} + onError={e => { e.currentTarget.onerror = null; e.currentTarget.src = thumbUrl }} + /> +
+ )} +
+ ) +} + +// ── 语音 ────────────────────────────────────── +function VoiceMsg({ msgKey, content, msgType }) { + const [aiText, setAiText] = useState('') + const [aiLoading, setAiLoading] = useState(false) + const [aiError, setAiError] = useState('') + const [audioError, setAudioError] = useState(false) + + useEffect(() => { + setAudioError(false) + }, [msgKey]) + + const handleToText = async () => { + setAiLoading(true) + setAiError('') + try { + const result = await aiParse(msgType, msgKey) + setAiText(result.text || result.result || JSON.stringify(result)) + } catch (e) { + setAiError(e.message) + } finally { + setAiLoading(false) + } + } + + return ( +
+
+ + {msgKey ? ( + audioError ? ( + 语音文件暂不可用 + ) : ( +
+ + {/* 转文字结果 */} + {(aiText || aiError) && ( +
+ {aiError ? `转文字失败:${aiError}` : `"${aiText}"`} +
+ )} +
+ ) +} + +// ── 视频 ────────────────────────────────────── +function VideoMsg({ msgKey, mediaPath, msgType }) { + const [showVideo, setShowVideo] = useState(false) + const [thumbErr, setThumbErr] = useState(false) + const [videoErr, setVideoErr] = useState(false) + const [aiText, setAiText] = useState('') + const [aiLoading, setAiLoading] = useState(false) + const [aiError, setAiError] = useState('') + + if (!msgKey && !mediaPath) return } label="视频" /> + + const thumbUrl = msgKey ? mediaUrl('video', msgKey) + '?thumb=1' : '' + const videoUrl = mediaPath ? mediaUrl('video', mediaPath) : mediaUrl('video', msgKey) + + const handleAiDesc = async () => { + setAiLoading(true) + setAiError('') + try { + const result = await aiParse(msgType, msgKey) + setAiText(result.text || result.result || JSON.stringify(result)) + } catch (e) { + setAiError(e.message) + } finally { + setAiLoading(false) + } + } + + return ( +
+ {showVideo && !videoErr ? ( +
+
+ ) : ( + /* 封面 / 播放按钮 */ +
{ + if (videoErr) return + setShowVideo(true) + }} + > + {!thumbErr && ( + 视频封面 setThumbErr(true)} + /> + )} + {/* 播放 / 错误图标 */} +
+ {videoErr + ? + : + } +
+ {videoErr && ( +
+ 视频加载失败 +
+ )} + {/* AI 描述按钮 */} +
{ e.stopPropagation(); handleAiDesc() }} + > + +
+
+ )} + + {/* AI 描述结果 */} + {(aiText || aiError) && ( +
+ {aiError ? `AI 识别失败:${aiError}` : aiText} +
+ )} +
+ ) +} + +// ── 链接 ────────────────────────────────────── +function LinkMsg({ msg }) { + const title = msg.linkTitle || '分享链接' + const desc = msg.linkDesc || '' + const url = msg.linkUrl || '' + const thumb = msg.linkThumb || '' + const source = msg.linkSource || '' + + return ( +
url && window.open(url, '_blank')} + style={{ + maxWidth: 280, borderRadius: 8, overflow: 'hidden', + background: 'var(--surface-2)', border: '1px solid var(--border)', + cursor: url ? 'pointer' : 'default', + }} + > +
+
+
+ {title} +
+ {desc && ( +
+ {desc} +
+ )} +
+ {thumb && ( + { e.currentTarget.style.display = 'none' }} + /> + )} +
+
+ + + {source || (url ? (() => { try { return new URL(url).hostname } catch { return url.slice(0, 30) } })() : '链接')} + +
+
+ ) +} + +// ── AI 按钮(图片/视频浮层用) ────────────── +function AiBtn({ loading, onClick }) { + return ( + + ) +} + +// ── 通用占位标签 ────────────────────────────── +function MediaTag({ icon, label }) { + return ( + + {icon} {label} + + ) +} + +// ── 工具函数 ────────────────────────────────── +const AVATAR_COLORS = [ + '#6366f1', '#8b5cf6', '#ec4899', '#f59e0b', + '#10b981', '#3b82f6', '#06b6d4', '#84cc16', +] + +function getAvatarColor(id) { + if (id === 'me') return '#6366f1' + let hash = 0 + for (let c of (id || '')) hash = (hash * 31 + c.charCodeAt(0)) & 0xffffffff + return AVATAR_COLORS[Math.abs(hash) % AVATAR_COLORS.length] +} + +function getFileExt(name) { + const m = String(name || '').match(/\.([a-z0-9]{1,8})(?:\?|#)?$/i) + return m ? m[1].toUpperCase() : '' +} + +function getTypeName(type) { + const names = { + 1: '图片', 2: '语音', 3: '视频', 4: '文件', + 5: '表情包', 7: '链接', 8: '位置', + 20: '红包', 21: '转账', 22: '拍一拍', + 25: '引用回复', 80: '系统消息', 81: '撤回', + } + return names[type] || `未知(${type})` +} diff --git a/chatlab-web/frontend/src/components/ReportDocumentView.jsx b/chatlab-web/frontend/src/components/ReportDocumentView.jsx new file mode 100644 index 0000000..8bdaded --- /dev/null +++ b/chatlab-web/frontend/src/components/ReportDocumentView.jsx @@ -0,0 +1,127 @@ +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' + +export const WORD_PAGE_CLASS = 'report-word-page' + +export const WORD_PAGE_CSS = ` +.${WORD_PAGE_CLASS} { + width: min(100%, 794px); + min-height: 1123px; + margin: 0 auto; + padding: 56px 64px; + box-sizing: border-box; + background: #ffffff; + color: #1f2937; + border: 1px solid #d8dee8; + box-shadow: 0 10px 32px rgba(15, 23, 42, 0.12); + font-family: "Microsoft YaHei", "SimSun", Arial, sans-serif; + font-size: 14px; + line-height: 1.75; +} +.${WORD_PAGE_CLASS} h1 { + margin: 0 0 22px; + padding-bottom: 12px; + border-bottom: 2px solid #111827; + color: #111827; + font-size: 24px; + line-height: 1.35; + text-align: center; + font-weight: 700; +} +.${WORD_PAGE_CLASS} h2 { + margin: 26px 0 12px; + padding-bottom: 6px; + border-bottom: 1px solid #d1d5db; + color: #111827; + font-size: 17px; + line-height: 1.45; + font-weight: 700; +} +.${WORD_PAGE_CLASS} h3 { + margin: 18px 0 8px; + color: #1f2937; + font-size: 15px; + line-height: 1.45; + font-weight: 700; +} +.${WORD_PAGE_CLASS} p { + margin: 8px 0; +} +.${WORD_PAGE_CLASS} ul, +.${WORD_PAGE_CLASS} ol { + margin: 8px 0 12px; + padding-left: 24px; +} +.${WORD_PAGE_CLASS} li { + margin: 4px 0; +} +.${WORD_PAGE_CLASS} table { + width: 100%; + margin: 12px 0 16px; + border-collapse: collapse; + table-layout: fixed; + font-size: 13px; +} +.${WORD_PAGE_CLASS} th, +.${WORD_PAGE_CLASS} td { + padding: 8px 10px; + border: 1px solid #9ca3af; + vertical-align: top; + word-break: break-word; +} +.${WORD_PAGE_CLASS} th { + background: #f3f4f6; + color: #111827; + font-weight: 700; + text-align: left; +} +.${WORD_PAGE_CLASS} blockquote { + margin: 10px 0; + padding: 8px 12px; + border-left: 4px solid #9ca3af; + background: #f9fafb; +} +.${WORD_PAGE_CLASS} code { + padding: 1px 4px; + background: #f3f4f6; + border-radius: 3px; + font-family: Consolas, monospace; + font-size: 12px; +} +.${WORD_PAGE_CLASS} hr { + margin: 20px 0; + border: none; + border-top: 1px solid #d1d5db; +} +.${WORD_PAGE_CLASS} img { + display: block; + max-width: 100%; + max-height: 360px; + margin: 10px 0 8px; + padding: 4px; + box-sizing: border-box; + border: 1px solid #d1d5db; + border-radius: 6px; + background: #f9fafb; + object-fit: contain; +} +@media (max-width: 900px) { + .${WORD_PAGE_CLASS} { + min-height: auto; + padding: 32px 24px; + } +} +` + +export default function ReportDocumentView({ content = '' }) { + return ( + <> + +
+ + {content || '(暂无报告内容)'} + +
+ + ) +} diff --git a/chatlab-web/frontend/src/index.css b/chatlab-web/frontend/src/index.css new file mode 100644 index 0000000..0921fc3 --- /dev/null +++ b/chatlab-web/frontend/src/index.css @@ -0,0 +1,817 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap'); + +/* ─── Design Tokens ───────────────────────────────────────────────── */ +:root { + /* Colors - Light Mode */ + --bg-base: #f4f6f9; + --bg-surface: #ffffff; + --bg-elevated: #ffffff; + --bg-overlay: #f0f2f5; + --bg-hover: #eef0f5; + + /* 别名:兼容组件中使用的 var(--surface) / var(--surface-2) */ + --surface: #ffffff; + --surface-2: #f0f2f5; + + --border: rgba(0,0,0,0.08); + --border-strong: rgba(0,0,0,0.14); + + --text-primary: #1a1d27; + --text-secondary: #5a6072; + --text-muted: #9ba3b8; + --text-inverse: #ffffff; + + /* Brand */ + --accent: #6366f1; + --accent-light: #4f46e5; + --accent-dim: rgba(99,102,241,0.10); + --accent-hover: #5254cc; + + /* Semantic */ + --success: #16a34a; + --success-dim: rgba(22,163,74,0.10); + --warning: #d97706; + --warning-dim: rgba(217,119,6,0.10); + --danger: #dc2626; + --danger-dim: rgba(220,38,38,0.10); + --info: #0284c7; + --info-dim: rgba(2,132,199,0.10); + + /* Sidebar */ + --sidebar-width: 280px; + --topbar-height: 56px; + + /* Spacing */ + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 14px; + --radius-xl: 20px; + + /* Shadows */ + --shadow-sm: 0 1px 3px rgba(0,0,0,0.08); + --shadow-md: 0 4px 12px rgba(0,0,0,0.10); + --shadow-lg: 0 8px 32px rgba(0,0,0,0.14); + + /* Transitions */ + --transition: 0.18s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* ─── Reset ───────────────────────────────────────────────────────── */ +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { font-size: 14px; } + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + background: var(--bg-base); + color: var(--text-primary); + line-height: 1.6; + -webkit-font-smoothing: antialiased; + overflow: hidden; + height: 100vh; +} + +#root { height: 100vh; display: flex; flex-direction: column; } + +/* ─── Scrollbar ───────────────────────────────────────────────────── */ +::-webkit-scrollbar { width: 5px; height: 5px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: #d1d5e0; border-radius: 3px; } +::-webkit-scrollbar-thumb:hover { background: #b0b7c8; } + +/* ─── Typography ──────────────────────────────────────────────────── */ +h1, h2, h3, h4 { font-weight: 600; letter-spacing: -0.02em; } +code, pre { font-family: 'JetBrains Mono', monospace; } + +/* ─── Layout Shell ────────────────────────────────────────────────── */ +.app-shell { + display: flex; + height: 100vh; + overflow: hidden; +} + +/* ─── Sidebar ─────────────────────────────────────────────────────── */ +.sidebar { + width: var(--sidebar-width); + min-width: var(--sidebar-width); + background: #f7f8fb; + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + overflow: hidden; +} + +.sidebar-logo { + height: var(--topbar-height); + padding: 0 20px; + display: flex; + align-items: center; + gap: 10px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} + +.sidebar-logo-icon { + width: 28px; height: 28px; + background: var(--bg-surface); + border-radius: var(--radius-sm); + display: flex; align-items: center; justify-content: center; + overflow: hidden; + flex-shrink: 0; +} + +.sidebar-logo-icon img { + width: 100%; + height: 100%; + object-fit: contain; + display: block; +} + +.sidebar-logo-text { + font-weight: 700; + font-size: 14px; + color: var(--text-primary); + white-space: nowrap; +} + +.sidebar-logo-version { + font-size: 10px; + color: var(--text-muted); + margin-left: auto; +} + +.sidebar-nav { + padding: 12px 10px; + flex-shrink: 0; + border-bottom: 1px solid var(--border); +} + +.nav-item { + display: flex; + align-items: center; + gap: 10px; + padding: 9px 12px; + border-radius: var(--radius-sm); + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition); + text-decoration: none; + font-size: 13.5px; + font-weight: 500; +} + +.nav-item:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.nav-item.active { + background: var(--accent-dim); + color: var(--accent-light); +} + +.nav-item svg { opacity: 0.8; flex-shrink: 0; } +.nav-item.active svg { opacity: 1; } + +/* ─── Room List (sidebar) ─────────────────────────────────────────── */ +.room-list { + flex: 1; + overflow-y: auto; + padding: 8px 10px; +} + +.room-list-header { + font-size: 11px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.08em; + padding: 6px 4px 4px; +} + +.room-item { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + border-radius: var(--radius-sm); + cursor: pointer; + transition: background var(--transition); +} + +.room-item:hover { background: var(--bg-hover); } +.room-item.active { background: var(--accent-dim); } + +.room-avatar { + width: 34px; height: 34px; + border-radius: var(--radius-sm); + background: var(--bg-overlay); + display: flex; align-items: center; justify-content: center; + font-size: 14px; + flex-shrink: 0; + color: var(--text-secondary); + font-weight: 600; +} + +.room-info { flex: 1; min-width: 0; } +.room-name { + font-size: 13px; + font-weight: 500; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.room-meta { + font-size: 11px; + color: var(--text-muted); +} + +/* ─── Main Content Area ───────────────────────────────────────────── */ +.main-content { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + background: var(--bg-base); +} + +/* ─── Topbar ──────────────────────────────────────────────────────── */ +.topbar { + height: var(--topbar-height); + background: var(--bg-surface); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + padding: 0 20px; + gap: 12px; + flex-shrink: 0; +} + +.topbar-title { + font-size: 15px; + font-weight: 600; + color: var(--text-primary); +} + +.topbar-subtitle { + font-size: 12px; + color: var(--text-muted); +} + +.topbar-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; } + +/* ─── Filter Bar ──────────────────────────────────────────────────── */ +.filter-bar { + background: var(--bg-surface); + border-bottom: 1px solid var(--border); + padding: 12px 20px; + display: flex; + align-items: flex-start; + gap: 12px; + flex-wrap: wrap; + flex-shrink: 0; +} + +.filter-group { + display: flex; + flex-direction: column; + gap: 4px; +} + +.filter-label { + font-size: 11px; + font-weight: 500; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.filter-input, .filter-select { + background: #ffffff; + border: 1px solid var(--border); + border-radius: var(--radius-sm); + color: var(--text-primary); + padding: 7px 11px; + font-size: 13px; + font-family: inherit; + transition: border-color var(--transition); + outline: none; + min-width: 160px; +} + +.filter-input:focus, .filter-select:focus { + border-color: var(--accent); + box-shadow: 0 0 0 2px var(--accent-dim); +} + +.filter-input::placeholder { color: var(--text-muted); } + +/* Date range */ +.date-range { + display: flex; + align-items: center; + gap: 6px; +} + +.date-range-sep { color: var(--text-muted); font-size: 12px; } + +/* Quick date chips */ +.date-chips { display: flex; gap: 4px; } + +.chip { + padding: 4px 10px; + border-radius: 20px; + font-size: 11.5px; + font-weight: 500; + cursor: pointer; + transition: all var(--transition); + border: 1px solid var(--border); + background: transparent; + color: var(--text-secondary); +} + +.chip:hover { border-color: var(--accent); color: var(--accent-light); } +.chip.active { background: var(--accent-dim); border-color: var(--accent); color: var(--accent-light); } + +/* ─── Message List ────────────────────────────────────────────────── */ +.message-area { + flex: 1; + overflow-y: auto; + padding: 20px; + display: flex; + flex-direction: column; + gap: 2px; +} + +.msg-day-divider { + display: flex; + align-items: center; + gap: 10px; + padding: 16px 0 8px; +} + +.msg-day-divider-line { + flex: 1; + height: 1px; + background: var(--border); +} + +.msg-day-divider-text { + font-size: 11px; + color: var(--text-muted); + font-weight: 500; + white-space: nowrap; + padding: 2px 10px; + background: var(--bg-overlay); + border-radius: 10px; +} + +/* Message bubble */ +.msg-row { + display: flex; + gap: 10px; + padding: 4px 6px; + border-radius: var(--radius-md); + transition: background var(--transition); +} + +.msg-row:hover { background: #f0f2f7; } +.msg-row.mine { flex-direction: row-reverse; } +.msg-row.highlight { background: rgba(99,102,241,0.06); } + +.msg-avatar { + width: 34px; height: 34px; + border-radius: var(--radius-sm); + background: var(--bg-overlay); + display: flex; align-items: center; justify-content: center; + font-size: 13px; + font-weight: 700; + flex-shrink: 0; + color: var(--text-secondary); + align-self: flex-start; + margin-top: 2px; +} + +.msg-body { flex: 1; min-width: 0; max-width: 72%; } +.msg-row.mine .msg-body { align-items: flex-end; display: flex; flex-direction: column; } + +.msg-meta { + display: flex; + align-items: baseline; + gap: 6px; + margin-bottom: 3px; +} + +.msg-row.mine .msg-meta { flex-direction: row-reverse; } + +.msg-sender { + font-size: 12px; + font-weight: 600; + color: var(--accent-light); +} + +.msg-nickname { + font-size: 11px; + color: var(--text-muted); +} + +.msg-time { + font-size: 11px; + color: var(--text-muted); + margin-left: auto; +} + +.msg-row.mine .msg-time { margin-left: 0; margin-right: auto; } + +.msg-bubble { + display: inline-block; + padding: 9px 13px; + border-radius: var(--radius-md); + font-size: 13.5px; + line-height: 1.55; + word-break: break-word; + background: #eef0f5; + color: var(--text-primary); + border: 1px solid var(--border); + max-width: 100%; +} + +.msg-row.mine .msg-bubble { + background: var(--accent); + border-color: transparent; + color: #fff; +} + +/* New message badge */ +.new-badge { + display: inline-block; + font-size: 10px; + padding: 1px 6px; + border-radius: 8px; + background: var(--success-dim); + color: var(--success); + font-weight: 600; + margin-left: 6px; + vertical-align: middle; +} + +/* ─── Status Bar ──────────────────────────────────────────────────── */ +.status-bar { + height: 34px; + background: var(--bg-surface); + border-top: 1px solid var(--border); + display: flex; + align-items: center; + padding: 0 20px; + gap: 16px; + flex-shrink: 0; +} + +.status-dot { + width: 7px; height: 7px; + border-radius: 50%; + background: var(--text-muted); +} + +.status-dot.connected { background: var(--success); animation: pulse 2s infinite; } +.status-dot.connecting { background: var(--warning); } + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +.status-text { + font-size: 11.5px; + color: var(--text-muted); +} + +/* ─── Buttons ─────────────────────────────────────────────────────── */ +.btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 7px 14px; + border-radius: var(--radius-sm); + font-size: 13px; + font-weight: 500; + cursor: pointer; + border: none; + transition: all var(--transition); + font-family: inherit; + white-space: nowrap; +} + +.btn-primary { + background: var(--accent); + color: #fff; +} + +.btn-primary:hover { background: var(--accent-hover); } + +.btn-ghost { + background: transparent; + color: var(--text-secondary); + border: 1px solid var(--border); +} + +.btn-ghost:hover { background: var(--bg-hover); color: var(--text-primary); } + +.btn-sm { padding: 5px 10px; font-size: 12px; } + +/* ─── Empty State ─────────────────────────────────────────────────── */ +.empty-state { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + color: var(--text-muted); + padding: 40px; +} + +.empty-state-icon { + font-size: 40px; + opacity: 0.4; +} + +.empty-state-title { + font-size: 15px; + font-weight: 600; + color: var(--text-secondary); +} + +.empty-state-desc { + font-size: 13px; + text-align: center; + line-height: 1.7; +} + +/* ─── Loading ─────────────────────────────────────────────────────── */ +.loading-spinner { + width: 20px; height: 20px; + border: 2px solid var(--border); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin 0.7s linear infinite; +} + +@keyframes spin { to { transform: rotate(360deg); } } + +.loading-overlay { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + color: var(--text-muted); + font-size: 13px; +} + +/* ─── Member Selector Panel ───────────────────────────────────────── */ +.member-selector { + position: relative; +} + +.member-selector-trigger { + display: flex; + align-items: center; + gap: 6px; + padding: 7px 11px; + background: #ffffff; + border: 1px solid var(--border); + border-radius: var(--radius-sm); + cursor: pointer; + font-size: 13px; + color: var(--text-secondary); + transition: border-color var(--transition); + min-width: 160px; +} + +.member-selector-trigger:hover, .member-selector-trigger.open { + border-color: var(--accent); +} + +.member-selector-dropdown { + position: absolute; + top: calc(100% + 4px); + left: 0; + width: 260px; + background: #ffffff; + border: 1px solid var(--border-strong); + border-radius: var(--radius-md); + box-shadow: var(--shadow-lg); + z-index: 100; + overflow: hidden; +} + +.member-search { + padding: 8px; + border-bottom: 1px solid var(--border); +} + +.member-search-input { + width: 100%; + background: var(--bg-overlay); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + color: var(--text-primary); + padding: 6px 10px; + font-size: 12.5px; + font-family: inherit; + outline: none; +} + +.member-search-input:focus { border-color: var(--accent); } +.member-search-input::placeholder { color: var(--text-muted); } + +.member-list-scroll { + max-height: 220px; + overflow-y: auto; + padding: 4px; +} + +.member-list-section-title { + font-size: 10.5px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.06em; + padding: 6px 8px 3px; +} + +.member-option { + display: flex; + align-items: center; + gap: 8px; + padding: 7px 8px; + border-radius: var(--radius-sm); + cursor: pointer; + transition: background var(--transition); +} + +.member-option:hover { background: var(--bg-hover); } +.member-option.checked { background: var(--accent-dim); } + +.member-checkbox { + width: 15px; height: 15px; + border: 1.5px solid var(--border-strong); + border-radius: 4px; + display: flex; align-items: center; justify-content: center; + flex-shrink: 0; + background: transparent; + transition: all var(--transition); +} + +.member-option.checked .member-checkbox { + background: var(--accent); + border-color: var(--accent); + color: #fff; +} + +.member-option-name { font-size: 12.5px; color: var(--text-primary); font-weight: 500; } +.member-option-nick { font-size: 11px; color: var(--text-muted); } + +.member-footer { + border-top: 1px solid var(--border); + padding: 8px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.member-selected-tags { + display: flex; + flex-wrap: wrap; + gap: 4px; + flex: 1; +} + +.member-tag { + display: flex; + align-items: center; + gap: 3px; + padding: 2px 7px; + background: var(--accent-dim); + border-radius: 10px; + font-size: 11px; + color: var(--accent-light); +} + +.member-tag-remove { + cursor: pointer; + opacity: 0.6; + font-size: 11px; +} + +.member-tag-remove:hover { opacity: 1; } + +/* ─── Webhook test pill ───────────────────────────────────────────── */ +.webhook-pill { + display: flex; + align-items: center; + gap: 6px; + padding: 5px 12px; + background: var(--success-dim); + border: 1px solid rgba(34,197,94,0.2); + border-radius: 20px; + font-size: 12px; + color: var(--success); + cursor: pointer; + transition: all var(--transition); +} + +.webhook-pill:hover { background: rgba(34,197,94,0.2); } + +/* ─── Toast / Notification ────────────────────────────────────────── */ +.toast-container { + position: fixed; + bottom: 24px; + right: 24px; + display: flex; + flex-direction: column-reverse; + gap: 8px; + z-index: 9999; +} + +.toast { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 16px; + background: #ffffff; + border: 1px solid var(--border-strong); + border-radius: var(--radius-md); + box-shadow: var(--shadow-md); + font-size: 13px; + color: var(--text-primary); + animation: slideIn 0.2s ease-out; + max-width: 320px; +} + +.toast.success { border-left: 3px solid var(--success); } +.toast.warning { border-left: 3px solid var(--warning); } + +@keyframes slideIn { + from { transform: translateX(20px); opacity: 0; } + to { transform: translateX(0); opacity: 1; } +} + +/* ─── Highlight search keyword ────────────────────────────────────── */ +mark { + background: rgba(245,158,11,0.3); + color: var(--warning); + border-radius: 2px; + padding: 0 1px; +} + +/* ─── Utilities ───────────────────────────────────────────────────── */ +.flex { display: flex; } +.flex-col { flex-direction: column; } +.items-center { align-items: center; } +.gap-2 { gap: 8px; } +.ml-auto { margin-left: auto; } +.text-muted { color: var(--text-muted); } +.text-sm { font-size: 12px; } +.truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + +/* ─── 消息数据同步进度条 ───────────────────────────────────────────── */ +.sync-progress-banner { + margin: 10px 16px 4px; + padding: 10px 14px; + background: var(--surface-2); + border: 1px solid var(--border); + border-radius: 8px; + flex-shrink: 0; +} + +.sync-progress-info { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 12px; + color: var(--text-muted); + margin-bottom: 6px; +} + +.sync-progress-track { + height: 4px; + background: var(--border); + border-radius: 2px; + overflow: hidden; +} + +.sync-progress-fill { + height: 100%; + background: var(--accent, #6366f1); + border-radius: 2px; + transition: width 0.5s ease; + min-width: 4px; +} diff --git a/chatlab-web/frontend/src/main.jsx b/chatlab-web/frontend/src/main.jsx new file mode 100644 index 0000000..89f91e5 --- /dev/null +++ b/chatlab-web/frontend/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App.jsx' +import './index.css' + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/chatlab-web/frontend/src/pages/ChatlogPage.jsx b/chatlab-web/frontend/src/pages/ChatlogPage.jsx new file mode 100644 index 0000000..b787e3f --- /dev/null +++ b/chatlab-web/frontend/src/pages/ChatlogPage.jsx @@ -0,0 +1,491 @@ +import { useState, useEffect, useRef, useCallback } from 'react' +import dayjs from 'dayjs' +import { Search, RefreshCw, Sparkles, Zap } from 'lucide-react' +import MemberSelector from '../components/MemberSelector' +import MessageBubble from '../components/MessageBubble' +import AISummaryPanel from '../components/AISummaryPanel' +import { getChatlog, getChatroomMembers, subscribeWebhook } from '../api' + +const DATE_PRESETS = [ + { label: '今天', getDates: () => [dayjs().startOf('day'), dayjs()] }, + { label: '昨天', getDates: () => [dayjs().subtract(1, 'day').startOf('day'), dayjs().subtract(1, 'day').endOf('day')] }, + { label: '近7天', getDates: () => [dayjs().subtract(7, 'day').startOf('day'), dayjs()] }, + { label: '近30天', getDates: () => [dayjs().subtract(30, 'day').startOf('day'), dayjs()] }, +] + +function getApiErrorMessage(e, fallback = '未知错误') { + return e?.response?.data?.detail || e?.response?.data?.error || e?.message || fallback +} + +const WARMUP_RETRY_LIMIT = 6 +const WARMUP_RETRY_DELAY_MS = 1500 + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +function isWarmupError(e) { + const message = getApiErrorMessage(e, '').toLowerCase() + return ( + message.includes('自动解密') || + message.includes('消息索引') || + message.includes('time range not found') || + message.includes('message index') + ) +} + +export default function ChatlogPage({ room, onNewMessage }) { + const [messages, setMessages] = useState([]) + const [members, setMembers] = useState([]) + const [loading, setLoading] = useState(false) + const [hasSearched, setHasSearched] = useState(false) + const [newMsgIds, setNewMsgIds] = useState(new Set()) + const [showAI, setShowAI] = useState(false) + const [errorMsg, setErrorMsg] = useState('') + const [earliestOffset, setEarliestOffset] = useState(0) + const [hasMore, setHasMore] = useState(false) + const [loadingMore, setLoadingMore] = useState(false) + + // 筛选状态 + const [startDate, setStartDate] = useState(dayjs().subtract(7, 'day').startOf('day').format('YYYY-MM-DDTHH:mm')) + const [endDate, setEndDate] = useState(dayjs().format('YYYY-MM-DDTHH:mm')) + const [selectedMembers, setSelectedMembers] = useState([]) + const [keyword, setKeyword] = useState('') + const [activePreset, setActivePreset] = useState('近7天') + + // Webhook / SSE + const [webhookConnected, setWebhookConnected] = useState(false) + const webhookConnectedRef = useRef(false) + const noScrollRef = useRef(false) + const bottomRef = useRef(null) + const scrollAreaRef = useRef(null) + const unsubRef = useRef(null) + + // 切换群时重置筛选并自动拉取 + useEffect(() => { + if (!room) return + setMessages([]) + setHasSearched(false) + setSelectedMembers([]) + setErrorMsg('') + setEarliestOffset(0) + setHasMore(false) + + // 切群时同时重置日期/关键词/预设为「近7天」,防止旧状态污染新群的查询 + const newStart = dayjs().subtract(7, 'day').startOf('day').format('YYYY-MM-DDTHH:mm') + const newEnd = dayjs().format('YYYY-MM-DDTHH:mm') + setStartDate(newStart) + setEndDate(newEnd) + setKeyword('') + setActivePreset('近7天') + + // 切换群时断开旧 SSE,如果之前已连接则自动重连新群 + const wasConnected = webhookConnectedRef.current + if (unsubRef.current) { + unsubRef.current() + unsubRef.current = null + webhookConnectedRef.current = false + setWebhookConnected(false) + } + + // 只有群聊(@chatroom 结尾)才拉取成员列表 + if (room.isGroup !== false) { + getChatroomMembers(room.id) + .then((res) => setMembers(res.data || [])) + .catch(() => setMembers([])) + } else { + setMembers([]) + } + + // 如果之前已连接,自动重连新群的 SSE + if (wasConnected) { + const unsub = subscribeWebhook(room.id, (msg) => { + setMessages((prev) => [...prev, msg]) + setNewMsgIds((prev) => new Set([...prev, msg.id])) + onNewMessage?.(room.id, msg) + setTimeout(() => { + setNewMsgIds((prev) => { + const next = new Set(prev) + next.delete(msg.id) + return next + }) + }, 3000) + }) + unsubRef.current = unsub + webhookConnectedRef.current = true + setWebhookConnected(true) + } + + // 同步触发查询,使用显式的 overrides 绕开 setState 时序 + // (这样即便新群默认 startDate 与上一个群相同也能正确触发) + fetchMessages(true, { + startDate: newStart, + endDate: newEnd, + keyword: '', + members: [], + activePreset: '近7天', + }) + }, [room]) // eslint-disable-line + + // 拉取聊天记录 + // overrides: 可选,绕开 state 闭包,直接用传入的筛选条件(切群自动加载场景使用) + const fetchMessages = useCallback(async (autoExpandIfEmpty = false, overrides = null) => { + if (!room) return + setLoading(true) + setErrorMsg('') + + const startTs = Date.now() + const _startDate = overrides?.startDate ?? startDate + const _endDate = overrides?.endDate ?? endDate + const _selectedMembers = overrides?.members ?? selectedMembers + const _keyword = overrides?.keyword ?? keyword + const _activePreset = overrides?.activePreset ?? activePreset + + try { + const LIMIT = 200 + const st = dayjs(_startDate).unix() + const et = dayjs(_endDate).unix() + let msgs = [] + let usedOffset = 0 + + for (let attempt = 0; attempt <= WARMUP_RETRY_LIMIT; attempt += 1) { + try { + const first = await getChatlog({ talker: room.id, startTime: st, endTime: et, senders: _selectedMembers, keyword: _keyword, limit: LIMIT, offset: 0 }) + const total = first.data?.total || 0 + msgs = first.data?.messages || [] + usedOffset = 0 + if (total > LIMIT) { + usedOffset = total - LIMIT + const last = await getChatlog({ talker: room.id, startTime: st, endTime: et, senders: _selectedMembers, keyword: _keyword, limit: LIMIT, offset: usedOffset }) + msgs = last.data?.messages || [] + } + + // 若近7天为空且 autoExpandIfEmpty,自动尝试近30天 + if (msgs.length === 0 && autoExpandIfEmpty && _activePreset === '近7天') { + const s30 = dayjs().subtract(30, 'day').startOf('day').unix() + const e30 = dayjs().unix() + const fallback = await getChatlog({ talker: room.id, startTime: s30, endTime: e30, senders: [], keyword: '', limit: LIMIT, offset: 0 }) + const fallbackMsgs = fallback.data?.messages || [] + if (fallbackMsgs.length > 0) { + setStartDate(dayjs().subtract(30, 'day').startOf('day').format('YYYY-MM-DDTHH:mm')) + setEndDate(dayjs().format('YYYY-MM-DDTHH:mm')) + setActivePreset('近30天') + msgs = fallbackMsgs + usedOffset = 0 + } + } + + // 保证 loading 至少显示 300ms,防止空状态闪烁 + const elapsed = Date.now() - startTs + if (elapsed < 300) await new Promise(r => setTimeout(r, 300 - elapsed)) + + break + } catch (e) { + if (attempt < WARMUP_RETRY_LIMIT && isWarmupError(e)) { + setErrorMsg(`自动解密仍在处理消息库,正在重试 ${attempt + 1}/${WARMUP_RETRY_LIMIT}...`) + await sleep(WARMUP_RETRY_DELAY_MS) + continue + } + throw e + } + } + + setMessages(msgs) + setEarliestOffset(usedOffset) + setHasMore(usedOffset > 0) + setHasSearched(true) + } catch (e) { + setErrorMsg('查询失败:' + getApiErrorMessage(e)) + } finally { + setLoading(false) + } + }, [room, startDate, endDate, selectedMembers, keyword, activePreset]) + + // 滚动到底部(加载更早消息时不滚动) + useEffect(() => { + if (noScrollRef.current) { noScrollRef.current = false; return } + bottomRef.current?.scrollIntoView({ behavior: 'smooth' }) + }, [messages]) + + // 加载更早的消息 + const loadEarlier = useCallback(async () => { + if (!room || loadingMore || earliestOffset <= 0) return + setLoadingMore(true) + noScrollRef.current = true + try { + const LIMIT = 200 + const st = dayjs(startDate).unix() + const et = dayjs(endDate).unix() + const newOffset = Math.max(0, earliestOffset - LIMIT) + const res = await getChatlog({ talker: room.id, startTime: st, endTime: et, senders: selectedMembers, keyword, limit: LIMIT, offset: newOffset }) + const older = res.data?.messages || [] + setMessages(prev => [...older, ...prev]) + setEarliestOffset(newOffset) + setHasMore(newOffset > 0) + } catch (e) { + setErrorMsg('加载失败:' + getApiErrorMessage(e)) + } finally { + setLoadingMore(false) + } + }, [room, earliestOffset, loadingMore, startDate, endDate, selectedMembers, keyword]) + + // 连接 / 断开 SSE Webhook + // 滚动到顶部自动加载更早消息 + useEffect(() => { + const el = scrollAreaRef.current + if (!el) return + const onScroll = () => { if (el.scrollTop === 0 && hasMore && !loadingMore) loadEarlier() } + el.addEventListener('scroll', onScroll) + return () => el.removeEventListener('scroll', onScroll) + }, [hasMore, loadingMore, loadEarlier]) + + const toggleWebhook = () => { + if (webhookConnected) { + unsubRef.current?.() + unsubRef.current = null + webhookConnectedRef.current = false + setWebhookConnected(false) + } else if (room) { + const unsub = subscribeWebhook(room.id, (msg) => { + setMessages((prev) => [...prev, msg]) + setNewMsgIds((prev) => new Set([...prev, msg.id])) + onNewMessage?.(room.id, msg) + setTimeout(() => { + setNewMsgIds((prev) => { + const next = new Set(prev) + next.delete(msg.id) + return next + }) + }, 3000) + }) + unsubRef.current = unsub + webhookConnectedRef.current = true + setWebhookConnected(true) + } + } + + // 应用日期预设 + const applyPreset = (preset) => { + const [s, e] = preset.getDates() + setStartDate(s.format('YYYY-MM-DDTHH:mm')) + setEndDate(e.format('YYYY-MM-DDTHH:mm')) + setActivePreset(preset.label) + } + + // 按天分组 + const grouped = groupByDay(messages) + + if (!room) { + return ( +
+
💬
+
请从左侧选择一个会话
+
选择会话后即可查看聊天记录
+
+ ) + } + + return ( + <> + {/* ── 筛选栏 ── */} +
+ {/* 日期快捷 */} +
+
快捷日期
+
+ {DATE_PRESETS.map((p) => ( +
applyPreset(p)} + > + {p.label} +
+ ))} +
+
+ + {/* 自定义日期范围 */} +
+
日期范围
+
+ { setStartDate(e.target.value); setActivePreset('') }} + style={{ minWidth: 155 }} + id="filter-start-date" + /> + + { setEndDate(e.target.value); setActivePreset('') }} + style={{ minWidth: 155 }} + id="filter-end-date" + /> +
+
+ + {/* 人员选择(仅群聊显示) */} + {members.length > 0 && ( +
+
发送人
+ +
+ )} + + {/* 关键词 */} +
+
关键词
+ setKeyword(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && fetchMessages()} + id="filter-keyword" + /> +
+ + {/* 操作按钮 */} +
+
 
+
+ + + {messages.length > 0 && ( + + )} +
+
+
+ + {/* ── 消息区域 ── */} +
+ {loading && ( +
+
+
正在加载聊天记录...
+
+ )} + + {!loading && loadingMore && ( +
加载更早的消息...
+ )} + + {!loading && errorMsg && ( +
+
⚠️
+
查询出错
+
{errorMsg}
+
+ )} + + {!loading && !errorMsg && hasSearched && messages.length === 0 && ( +
+
🔍
+
未找到聊天记录
+
尝试调整筛选条件,或扩大时间范围
+ {activePreset !== '近30天' && ( + + )} +
+ )} + + {!loading && grouped.map(({ day, msgs }) => ( +
+
+
+
{day}
+
+
+ {msgs.map((msg) => ( + + ))} +
+ ))} + +
+
+ + {/* ── AI 总结面板 ── */} + {showAI && ( + setShowAI(false)} + /> + )} + + {/* ── 状态栏 ── */} +
+
+ + {webhookConnected ? 'Webhook 实时接收中' : '未连接 Webhook'} + + {messages.length > 0 && ( + 共 {messages.length} 条消息 + )} +
+
+ + {webhookConnected ? '断开 Webhook' : '连接 Webhook'} +
+
+
+ + ) +} + +// 按天分组消息 +function groupByDay(messages) { + const groups = {} + for (const msg of messages) { + const day = dayjs.unix(msg.timestamp).format('YYYY年MM月DD日 dddd') + if (!groups[day]) groups[day] = [] + groups[day].push(msg) + } + return Object.entries(groups).map(([day, msgs]) => ({ day, msgs })) +} diff --git a/chatlab-web/frontend/src/pages/KnowledgePage.jsx b/chatlab-web/frontend/src/pages/KnowledgePage.jsx new file mode 100644 index 0000000..44de653 --- /dev/null +++ b/chatlab-web/frontend/src/pages/KnowledgePage.jsx @@ -0,0 +1,236 @@ +import { useState, useEffect, useCallback } from 'react' +import { Search, RefreshCw, Edit3, Check, X, Download } from 'lucide-react' +import dayjs from 'dayjs' +import { getKnowledge, getKnowledgeDoc, patchKnowledge } from '../api' +import ReportDocumentView from '../components/ReportDocumentView' +import { exportWordDoc } from '../utils/wordExport' + +export default function KnowledgePage({ onToast }) { + const [docs, setDocs] = useState([]) + const [selectedDoc, setSelectedDoc] = useState(null) + const [docDetail, setDocDetail] = useState(null) + const [keyword, setKeyword] = useState('') + const [loading, setLoading] = useState(false) + const [editing, setEditing] = useState(false) + const [editContent, setEditContent] = useState('') + const [saving, setSaving] = useState(false) + + const loadDocs = useCallback(async (kw) => { + setLoading(true) + try { + const data = await getKnowledge(kw) + setDocs(Array.isArray(data) ? data : []) + } catch { + setDocs([]) + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { loadDocs('') }, [loadDocs]) + + const handleSearch = () => loadDocs(keyword) + + const handleSelect = async (doc) => { + setSelectedDoc(doc) + setDocDetail(null) + setEditing(false) + try { + const detail = await getKnowledgeDoc(doc.id) + setDocDetail(detail) + setEditContent(detail.content || '') + } catch { + setDocDetail(null) + } + } + + const handleSave = async () => { + if (!selectedDoc) return + setSaving(true) + try { + await patchKnowledge(selectedDoc.id, editContent) + onToast?.('保存成功') + setEditing(false) + setDocDetail((prev) => prev ? { ...prev, content: editContent } : prev) + } catch { + onToast?.('保存失败', 'error') + } finally { + setSaving(false) + } + } + + const handleExport = async () => { + if (!selectedDoc || !docDetail?.content) { + onToast?.('暂无可导出的售后报告', 'error') + return + } + try { + await exportWordDoc(selectedDoc.title || `售后报告_${selectedDoc.id}`, docDetail.content) + onToast?.('Word 文档已导出') + } catch (e) { + onToast?.('Word 导出失败', 'error') + } + } + + return ( +
+ + {/* 左栏:售后报告列表 */} +
+
+
售后报告库
+
+ setKeyword(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + /> + + +
+
+ +
+ {loading && ( +
加载中...
+ )} + {!loading && docs.length === 0 && ( +
+
📚
+
暂无售后报告
+
在「AI 话题分析」中,选择话题后点击「AI 生成售后报告」即可生成
+
+ )} + {!loading && docs.length > 0 && (() => { + // 按群聊名称分组 + const groupMap = {} + docs.forEach(doc => { + const key = doc.group_name || '未知群聊' + if (!groupMap[key]) groupMap[key] = [] + groupMap[key].push(doc) + }) + return Object.entries(groupMap).map(([groupName, items]) => ( +
+
+ {groupName} +
+ {items.map((doc) => ( +
handleSelect(doc)} + style={{ + padding: '10px 14px', + cursor: 'pointer', + borderBottom: '1px solid var(--border)', + background: selectedDoc?.id === doc.id ? 'var(--bg-overlay)' : 'transparent', + }} + > +
+ {doc.title || `文档 #${doc.id}`} +
+
+ 更新于 {dayjs(doc.updated_at).format('MM-DD HH:mm')} +
+
+ ))} +
+ )) + })()} +
+
+ + {/* 右栏:文档详情 */} +
+ {!selectedDoc ? ( +
+
📄
+
请选择一篇售后报告
+
+ ) : ( + <> +
+
{selectedDoc.title || `文档 #${selectedDoc.id}`}
+
+ {editing ? ( + <> + + + + ) : ( + <> + + + + )} +
+
+ +
+ {!docDetail ? ( +
加载中...
+ ) : editing ? ( +