diff --git a/.gitignore b/.gitignore
index 274dc5b..2561b5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,9 @@ build/
.env
.env.*
!.env.example
+!apps/web/.env.production
+!apps/web/.env.staging
+!apps/web/.env.test
.DS_Store
.vscode/
diff --git a/apps/web/.browserslistrc b/apps/web/.browserslistrc
new file mode 100644
index 0000000..21e47a5
--- /dev/null
+++ b/apps/web/.browserslistrc
@@ -0,0 +1 @@
+chrome >= 90
diff --git a/apps/web/.env.production b/apps/web/.env.production
new file mode 100644
index 0000000..c19eedf
--- /dev/null
+++ b/apps/web/.env.production
@@ -0,0 +1,8 @@
+PUBLIC_ENV=xuyue.cc
+PUBLIC_MAXKB_URL=https://maxkb.xuyue.cc/chat/api/embed?protocol=https&host=maxkb.xuyue.cc&token=dd37457027c40b39
+PUBLIC_OJ_URL=https://oj.xuyue.cc
+PUBLIC_CODE_URL=https://code.xuyue.cc
+PUBLIC_JUDGE0_URL=https://judge0api.xuyue.cc
+PUBLIC_SIGNALING_URL=wss://signaling.xuyue.cc
+PUBLIC_WS_URL=wss://oj.xuyue.cc/ws
+PUBLIC_ICONIFY_URL=https://icon.xuyue.cc
\ No newline at end of file
diff --git a/apps/web/.env.staging b/apps/web/.env.staging
new file mode 100644
index 0000000..7916890
--- /dev/null
+++ b/apps/web/.env.staging
@@ -0,0 +1,8 @@
+PUBLIC_ENV=school
+PUBLIC_MAXKB_URL=http://10.13.114.114:92/chat/api/embed?protocol=http&host=10.13.114.114:92&token=dd37457027c40b39
+PUBLIC_OJ_URL=http://10.13.114.114:81
+PUBLIC_CODE_URL=http://10.13.114.114:82
+PUBLIC_JUDGE0_URL=http://10.13.114.114:8082
+PUBLIC_ICONIFY_URL=http://10.13.114.114:8098
+PUBLIC_SIGNALING_URL=ws://10.13.114.114:8085
+PUBLIC_WS_URL=ws://10.13.114.114:81/ws
\ No newline at end of file
diff --git a/apps/web/.env.test b/apps/web/.env.test
new file mode 100644
index 0000000..cf7dc87
--- /dev/null
+++ b/apps/web/.env.test
@@ -0,0 +1,8 @@
+PUBLIC_ENV=test
+PUBLIC_MAXKB_URL=http://10.13.114.114:92/chat/api/embed?protocol=http&host=10.13.114.114:92&token=dd37457027c40b39
+PUBLIC_OJ_URL=http://10.13.114.114:93
+PUBLIC_CODE_URL=http://10.13.114.114:82
+PUBLIC_JUDGE0_URL=http://10.13.114.114:8082
+PUBLIC_ICONIFY_URL=http://10.13.114.114:8098
+PUBLIC_SIGNALING_URL=ws://10.13.114.114:8085
+PUBLIC_WS_URL=ws://10.13.114.114:93/ws
\ No newline at end of file
diff --git a/apps/web/.github/workflows/deploy.yml b/apps/web/.github/workflows/deploy.yml
new file mode 100644
index 0000000..79cfa2c
--- /dev/null
+++ b/apps/web/.github/workflows/deploy.yml
@@ -0,0 +1,46 @@
+name: Deploy
+
+on:
+ push:
+ branches:
+ - main
+
+permissions:
+ contents: read
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - name: debian
+ build_command: build
+ remote_port: 22
+ target: /root/OJDeploy/data/clientnext
+ - name: school
+ build_command: build:staging
+ remote_port: 8822
+ target: /root/OJ/data/dist
+ steps:
+ - uses: actions/checkout@v7
+
+ - uses: actions/setup-node@v7
+ with:
+ node-version: 24
+ cache: npm
+ - run: npm install
+ - run: npm run ${{ matrix.build_command }}
+ env:
+ CI: false
+
+ - uses: easingthemes/ssh-deploy@main
+ with:
+ SSH_PRIVATE_KEY: ${{ secrets.KEY }}
+ REMOTE_HOST: ${{ secrets.HOST }}
+ REMOTE_PORT: ${{ matrix.remote_port }}
+ ARGS: "-avzr --delete"
+ SOURCE: dist/
+ REMOTE_USER: root
+ TARGET: ${{ matrix.target }}
\ No newline at end of file
diff --git a/apps/web/.gitignore b/apps/web/.gitignore
new file mode 100644
index 0000000..e0280c2
--- /dev/null
+++ b/apps/web/.gitignore
@@ -0,0 +1,30 @@
+# 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?
+
+src/components.d.ts
+src/auto-imports.d.ts
+
+.claude
+.worktrees
\ No newline at end of file
diff --git a/apps/web/.prettierrc.toml b/apps/web/.prettierrc.toml
new file mode 100644
index 0000000..d165b8d
--- /dev/null
+++ b/apps/web/.prettierrc.toml
@@ -0,0 +1 @@
+semi=false
\ No newline at end of file
diff --git a/apps/web/CLAUDE.md b/apps/web/CLAUDE.md
new file mode 100644
index 0000000..7efe1f5
--- /dev/null
+++ b/apps/web/CLAUDE.md
@@ -0,0 +1,114 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+**ojnext** is the frontend for an Online Judge platform. Built with Vue 3 + TypeScript using Vite (Rolldown-based bundler), Naive UI component library, Pinia for state management, and Vue Router.
+
+## Commands
+
+```bash
+npm start # Start dev server on port 5173
+npm run build # Production build
+npm run build:staging # Staging build
+npm run build:test # Test build
+npm fmt # Format with Prettier
+```
+
+No test suite is configured. Linting is via Prettier only.
+
+## Architecture
+
+### Directory Structure
+
+```
+src/
+├── shared/ # Cross-cutting concerns: layout, stores, composables, API
+├── oj/ # User-facing features (problems, submissions, contests, etc.)
+├── admin/ # Admin panel features
+├── utils/ # Constants, types, HTTP client, helpers
+├── routes.ts # Route definitions (two top-level: ojs, admins)
+├── main.ts # App entry point
+└── App.vue # Root component with Naive UI theme setup
+```
+
+### Module Pattern
+
+Each feature module (under `oj/` or `admin/`) typically has:
+- `views/` — page-level Vue components
+- `components/` — feature-specific components
+- `api.ts` — API calls specific to the feature
+
+Shared logic lives in `shared/`:
+- `store/` — Pinia stores: `user` (auth/roles), `config` (site-wide settings), `authModal` (login/signup form state), `screenMode` (problem split-screen layout), `loginSummary` (AI activity summary)
+- `composables/` — `pagination` (URL-synced), `websocket` (reconnect + heartbeat), `sync` (Yjs/y-webrtc for collaborative editing), `configUpdate` (WS-pushed config sync), `useMermaid` (lazy Mermaid render), `breakpoints`, `maxkb`
+- `layout/` — `default.vue` and `admin.vue` layout wrappers
+- `api.ts` — shared API calls (auth, profile, tags, captcha)
+
+### Auto-Imports
+
+Configured via `unplugin-auto-import` and `unplugin-vue-components`. You do **not** need to manually import:
+- Vue APIs (`ref`, `computed`, `watch`, etc.)
+- Vue Router (`useRouter`, `useRoute`)
+- Pinia (`defineStore`, `storeToRefs`)
+- VueUse composables
+- Naive UI composables (`useDialog`, `useMessage`, `useNotification`, `useLoadingBar`)
+- Naive UI components (all `N*` components)
+- Naive UI types (`DataTableColumn`, `FormRules`, `FormItemRule`, `SelectOption`, `UploadCustomRequestOptions`, `UploadFileInfo`, `MenuOption`, `DropdownOption`)
+
+Generated type declaration files: `src/auto-imports.d.ts`, `src/components.d.ts`.
+
+### Path Aliases
+
+```
+utils → ./src/utils
+oj → ./src/oj
+admin → ./src/admin
+shared → ./src/shared
+```
+
+### HTTP Client
+
+`utils/http.ts` — Axios instance with interceptors. All API calls proxy through the dev server:
+- `/api` and `/public` → `PUBLIC_OJ_URL` (backend)
+- `/ws` → `PUBLIC_WS_URL` (WebSocket backend)
+
+### Key Utilities
+
+- `utils/constants.ts` — Judge status codes, language IDs, difficulty levels, contest types
+- `utils/types.ts` — TypeScript interfaces for all domain models
+- `utils/permissions.ts` — Permission check helpers
+- `utils/judge.ts` — Judge-related utilities
+- `utils/renders.ts` — Table column render helpers for Naive UI DataTable
+
+### Environment Variables
+
+Variables prefixed with `PUBLIC_` are injected at build time. Env files: `.env`, `.env.staging`, `.env.test`.
+
+| Variable | Purpose |
+|---|---|
+| `PUBLIC_OJ_URL` | Backend REST API base URL |
+| `PUBLIC_WS_URL` | WebSocket server URL |
+| `PUBLIC_ENV` | Environment name (dev/staging/production) |
+| `PUBLIC_CODE_URL` | Code execution service |
+| `PUBLIC_JUDGE0_URL` | Judge0 API |
+| `PUBLIC_MAXKB_URL` | Knowledge base service |
+| `PUBLIC_SIGNALING_URL` | WebRTC signaling server |
+| `PUBLIC_ICONIFY_URL` | Iconify icon CDN |
+
+### Routing
+
+Routes are defined in `src/routes.ts` with two root routes: `ojs` (user-facing) and `admins` (admin panel). Route meta fields used:
+- `requiresAuth` — redirect to login if not authenticated
+- `requiresSuperAdmin` — super admin only
+- `requiresProblemPermission` — problem management access
+
+### Real-time Features
+
+- WebSocket via composable in `shared/composables/` for submission status updates
+- Yjs + y-webrtc for collaborative editing in the flowchart editor
+
+## Related Repository
+
+The backend is at `../OnlineJudge` — a Django 5 + DRF project. See its CLAUDE.md for backend details.
diff --git a/apps/web/README.md b/apps/web/README.md
new file mode 100644
index 0000000..b18748c
--- /dev/null
+++ b/apps/web/README.md
@@ -0,0 +1 @@
+oj.xuyue.cc
\ No newline at end of file
diff --git a/apps/web/docs/API接口对比分析.md b/apps/web/docs/API接口对比分析.md
new file mode 100644
index 0000000..2a0cb71
--- /dev/null
+++ b/apps/web/docs/API接口对比分析.md
@@ -0,0 +1,548 @@
+# API接口对比分析
+
+## 更新日志
+
+### 最近更新(2025-01-15)
+- ✨ **FlowchartEditor 流程图编辑器**:新增完整的流程图编辑功能
+ - 基于 Vue Flow 构建的流程图编辑器组件
+ - 支持7种节点类型:开始、输入、处理、判断、循环、输出、结束
+ - 完整的拖拽创建、节点连接、编辑功能
+ - 撤销重做、自动保存、键盘快捷键支持
+ - 模块化设计,包含9个独立的功能模块
+- 🎨 **前端组件优化**:完善了组件文档和项目结构说明
+ - 更新了 README.md 中的技术栈和项目结构
+ - 创建了详细的 FlowchartEditor 使用文档
+ - 优化了开发指南和构建流程说明
+- 🔧 **构建工具升级**:从 Vite 迁移到 Rsbuild
+ - 使用 Rsbuild 作为新的构建工具
+ - 支持多环境构建(test、staging、production)
+ - 优化了构建性能和开发体验
+
+### 历史更新(2025-10)
+- ✨ **AI分析功能增强**:完善了AI智能分析模块的文档说明
+ - 详细说明了4个AI相关接口的功能和参数
+ - 新增等级系统说明(S/A/B/C),包含特殊规则
+ - 补充了时间范围选择功能
+ - 说明了流式响应的实现方式
+ - 前端组件从 `WeeklyChart.vue` 升级为 `DurationChart.vue`(混合图表)
+- 🔧 **数据缓存优化**:后端AI接口增加了缓存机制,提升性能
+- 🐛 **修正等级系统说明**:更正了等级阈值(A级:前35%,B级:前75%),并补充了小规模参与惩罚规则
+
+---
+
+## 一、前端已使用的API接口
+
+### 1. 用户认证相关(shared/api.ts)
+- `POST /api/login` - 用户登录
+- `POST /api/register` - 用户注册
+- `GET /api/logout` - 用户登出
+- `GET /api/profile` - 获取用户资料
+- `GET /api/captcha` - 获取验证码
+
+### 2. OJ普通用户API(oj/api.ts)
+#### 2.1 网站配置
+- `GET /api/website` - 获取网站配置
+- `GET /api/hitokoto` - 获取一言
+
+#### 2.2 题目相关
+- `GET /api/problem` - 获取题目列表
+- `GET /api/problem` - 获取单个题目
+- `GET /api/problem/tags` - 获取题目标签列表
+- `GET /api/problem/author` - 获取题目作者列表
+- `GET /api/problem/beat_count` - 获取题目击败率
+- `GET /api/pickone` - 随机获取题目
+- `GET /api/contest/problem` - 获取竞赛题目
+
+#### 2.3 提交相关
+- `GET /api/submission` - 获取单个提交
+- `POST /api/submission` - 提交代码
+- `GET /api/submissions` - 获取提交列表
+- `GET /api/submissions/today_count` - 获取今日提交数
+- `GET /api/contest_submissions` - 获取竞赛提交列表
+
+#### 2.4 排名相关
+- `GET /api/user_rank` - 获取用户排名
+- `GET /api/user_activity_rank` - 获取活跃度排名
+- `GET /api/user_problem_rank` - 获取题目排名
+- `GET /api/contest_rank` - 获取竞赛排名
+
+#### 2.5 竞赛相关
+- `GET /api/contests` - 获取竞赛列表
+- `GET /api/contest` - 获取单个竞赛
+- `GET /api/contest/access` - 获取竞赛访问权限
+- `POST /api/contest/password` - 验证竞赛密码
+
+#### 2.6 公告相关
+- `GET /api/announcement` - 获取公告列表/单个公告
+
+#### 2.7 消息相关
+- `POST /api/message` - 创建消息
+- `GET /api/message` - 获取消息列表
+
+#### 2.8 评论相关
+- `POST /api/comment` - 创建评论
+- `GET /api/comment` - 获取评论
+- `GET /api/comment/statistics` - 获取评论统计
+
+#### 2.9 用户相关
+- `POST /api/upload_avatar` - 上传头像
+- `PUT /api/profile` - 更新用户资料
+- `GET /api/profile/fresh_display_id` - 刷新用户题目显示ID
+- `GET /api/metrics` - 获取用户统计数据
+
+#### 2.10 教程相关
+- `GET /api/tutorial` - 获取单个教程
+- `GET /api/tutorials` - 获取教程列表
+
+#### 2.11 AI分析相关
+- `GET /api/ai/detail` - 获取用户详细数据
+ - **参数**: start, end(时间范围)
+ - **返回**: 用户等级(S/A/B/C)、已解决题目列表、标签统计、难度统计、参赛次数等
+ - **特点**: 包含班级排名对比,计算每道题的解题排名和等级
+- `GET /api/ai/duration` - 获取时段数据
+ - **参数**: end(结束时间), duration(时间单位,如 "months:6", "weeks:1")
+ - **返回**: 每周/每月的综合情况(题目数、提交数、等级)
+ - **用途**: 用于绘制时间趋势图,展示学习进度变化
+- `GET /api/ai/heatmap` - 获取热力图数据
+ - **返回**: 用户的提交热力图数据(按日期统计提交数)
+ - **用途**: 可视化用户活跃度分布
+- `POST /api/ai/analysis` - AI智能分析生成
+ - **请求体**: details(详细数据), duration(时段数据)
+ - **响应方式**: 流式响应(Server-Sent Events)
+ - **AI提供商**: DeepSeek
+ - **功能**: 根据用户学习数据生成个性化学习建议和鼓励
+ - **实现**: 使用fetch直接调用,在 `oj/store/ai.ts` 中处理流式输出
+
+### 3. 管理员API(admin/api.ts)
+#### 3.1 仪表板
+- `GET /api/admin/dashboard_info` - 获取仪表板信息
+- `GET /api/admin/random_user` - 随机获取用户
+
+#### 3.2 题目管理
+- `GET /api/admin/problem` - 获取题目列表/单个题目
+- `POST /api/admin/problem` - 创建题目
+- `PUT /api/admin/problem` - 编辑题目
+- `PUT /api/admin/problem/visible` - 切换题目可见性
+- `DELETE /api/admin/problem` - 删除题目
+- `GET /api/admin/contest/problem` - 获取竞赛题目
+- `POST /api/admin/contest/problem` - 创建竞赛题目
+- `PUT /api/admin/contest/problem` - 编辑竞赛题目
+- `DELETE /api/admin/contest/problem` - 删除竞赛题目
+- `POST /api/admin/contest/add_problem_from_public` - 从公开题库添加题目到竞赛
+
+#### 3.3 用户管理
+- `GET /api/admin/user` - 获取用户列表
+- `POST /api/admin/user` - 导入用户
+- `PUT /api/admin/user` - 编辑用户
+- `DELETE /api/admin/user` - 删除用户
+- `POST /api/admin/reset_password` - 重置用户密码
+
+#### 3.4 竞赛管理
+- `GET /api/admin/contest` - 获取竞赛列表/单个竞赛
+- `POST /api/admin/contest` - 创建竞赛
+- `PUT /api/admin/contest` - 编辑竞赛
+- `GET /api/admin/contest/acm_helper` - 获取ACM比赛辅助检查列表
+- `PUT /api/admin/contest/acm_helper` - 更新ACM比赛辅助检查状态
+
+#### 3.5 测试用例管理
+- `POST /api/admin/test_case` - 上传测试用例
+- `GET /api/admin/prune_test_case` - 列出无效测试用例
+- `DELETE /api/admin/prune_test_case` - 清理无效测试用例
+
+#### 3.6 题目管理扩展
+- `POST /api/admin/contest_problem/make_public` - 将竞赛题目转为公开题目
+
+#### 3.7 判题服务器管理
+- `GET /api/admin/judge_server` - 获取判题服务器列表
+- `DELETE /api/admin/judge_server` - 删除判题服务器
+
+#### 3.8 公告管理
+- `GET /api/admin/announcement` - 获取公告列表/单个公告
+- `POST /api/admin/announcement` - 创建公告
+- `PUT /api/admin/announcement` - 编辑公告
+- `DELETE /api/admin/announcement` - 删除公告
+
+#### 3.9 评论管理
+- `GET /api/admin/comment` - 获取评论列表
+- `DELETE /api/admin/comment` - 删除评论
+
+#### 3.10 网站配置
+- `GET /api/admin/website` - 获取网站配置
+- `POST /api/admin/website` - 更新网站配置
+
+#### 3.11 文件上传
+- `POST /api/admin/upload_image` - 上传图片(富文本编辑器、Markdown编辑器使用)
+
+#### 3.12 提交管理
+- `GET /api/admin/submission/rejudge` - 重新判题
+- `GET /api/admin/submission/statistics` - 获取提交统计
+
+#### 3.13 教程管理
+- `GET /api/admin/tutorial` - 获取教程列表/单个教程
+- `POST /api/admin/tutorial` - 创建教程
+- `PUT /api/admin/tutorial` - 更新教程
+- `DELETE /api/admin/tutorial` - 删除教程
+- `PUT /api/admin/tutorial/visibility` - 设置教程可见性
+
+---
+
+## 二、后端提供但前端未使用的API接口
+
+### 1. 用户认证相关(account)
+- `POST /api/change_password` - 修改密码
+- `POST /api/change_email` - 修改邮箱
+- `POST /api/apply_reset_password` - 申请重置密码
+- `POST /api/reset_password` - 重置密码
+- `GET /api/check_username_or_email` - 检查用户名或邮箱是否存在
+- `GET /api/tfa_required` - 检查是否需要双因素认证
+- `POST /api/two_factor_auth` - 双因素认证
+- `GET /api/sessions` - 会话管理
+- `GET /api/open_api_appkey` - OpenAPI密钥管理
+- `GET /api/sso` - 单点登录
+
+### 2. 用户管理(admin)
+- `GET /api/admin/generate_user` - 生成用户
+
+### 3. 网站配置相关
+- `GET /api/languages` - 获取支持的编程语言列表
+
+### 4. 判题服务器内部接口(不需要前端实现)
+- ❌ `POST /api/judge_server_heartbeat/` - 判题服务器心跳
+ - **标记为不需要**
+ - **原因**: 此接口由 JudgeServer Docker 容器调用,用于向后端报告服务器状态
+ - **使用方**: 判题服务器(非前端)
+ - **数据内容**: hostname, judger_version, CPU/内存使用率等
+ - **认证方式**: 使用特殊的 judge_server_token,非用户认证
+
+### 5. 管理员配置相关
+- `GET /api/admin/smtp` - SMTP配置
+- `POST /api/admin/smtp_test` - SMTP测试
+- `GET /api/admin/versions` - 版本信息
+
+### 6. 题目管理相关(admin)
+- `POST /api/admin/export_problem` - 导出题目
+- `POST /api/admin/import_problem` - 导入题目
+- `POST /api/admin/import_fps` - 导入FPS格式题目
+
+### 7. 竞赛相关(admin)
+- `GET /api/contest/announcement` - 获取竞赛公告列表(OJ端)
+- `GET /api/admin/contest/announcement` - 获取竞赛公告(管理端)
+- `GET /api/admin/download_submissions` - 下载竞赛提交
+
+### 8. 提交相关(不必要的接口)
+- ❌ `GET /api/submission_exists` - 检查提交是否存在
+ - **标记为不需要**
+ - **原因**: 题目接口返回的 `my_status` 字段已完整包含此信息
+ - **替代方案**: 直接判断 `my_status` 的值(0=已通过,非零=已尝试未通过,null=未尝试)
+ - **优势**: 零额外请求,性能更优
+
+### 9. 文件上传相关(admin)
+- `POST /api/admin/upload_file` - 上传文件(任意格式)
+ - **说明**: 前端已使用 `upload_image`(仅图片),`upload_file` 可上传任意文件
+ - **当前状态**: 未使用(前端暂无上传非图片文件的需求)
+ - **潜在场景**: 题目附件、教程资料、作业提交等
+
+---
+
+## 三、统计总结
+
+### 前端已使用接口统计
+- **OJ普通用户接口**: 38个(包括1个直接用fetch调用的流式接口)
+- **管理员接口**: 35个
+- **共享接口**: 5个
+- **总计**: 78个API调用
+
+### 后端未被使用接口统计
+- **用户认证相关**: 10个
+- **网站配置相关**: 2个(languages)
+- **题目管理相关**: 3个
+- **竞赛管理相关**: 3个
+- **其他**: 1个
+- **标记为不需要**: 2个
+ - submission_exists(数据冗余)
+ - judge_server_heartbeat(内部接口)
+- **总计**: 21个API端点(其中2个不需要前端实现)
+
+### 未使用接口占比
+约 **21%** 的后端API接口前端尚未使用
+- **需要考虑实现**: 19个接口
+- **不必要实现**: 2个接口(submission_exists, judge_server_heartbeat)
+
+**备注**: 本次更新新增了 ACM 比赛辅助检查功能(2个接口),用于赛后人工审核代码。
+
+---
+
+## 四、建议
+
+### 1. 高优先级需要实现的功能
+- **密码管理**: change_password, apply_reset_password, reset_password
+- **邮箱管理**: change_email
+- **用户名/邮箱检查**: check_username_or_email(注册时实时验证)
+- **编程语言列表**: languages(显示支持的编程语言)
+- **题目导入导出**: export_problem, import_problem(方便题库管理)
+
+### 2. 中等优先级功能
+- **双因素认证**: tfa_required, two_factor_auth(增强安全性)
+- **会话管理**: sessions(多设备登录管理)
+- **竞赛公告**: contest/announcement(OJ端,增强竞赛体验)
+
+### 3. 低优先级功能
+- **SSO单点登录**: sso(如需要集成其他系统)
+- **OpenAPI**: open_api_appkey(如需要开放API)
+- **SMTP配置**: smtp, smtp_test(管理员配置)
+- **版本信息**: versions(显示系统版本)
+- **FPS导入**: import_fps(特定格式题目导入)
+- **文件上传**: upload_file(目前只有图片上传)
+
+### 4. 可选功能
+- **生成用户**: generate_user(批量生成测试用户)
+- **下载提交**: download_submissions(下载竞赛所有提交)
+
+---
+
+## 五、接口使用率分析
+
+| 模块 | 后端提供 | 前端使用 | 使用率 |
+|------|---------|---------|--------|
+| 用户认证 | 17 | 7 | 41% |
+| 题目管理 | 14 | 11 | 79% |
+| 提交管理 | 7 | 6 | 86% |
+| 竞赛管理 | 12 | 9 | 75% |
+| 公告管理 | 4 | 4 | 100% |
+| 评论管理 | 4 | 4 | 100% |
+| 消息管理 | 1 | 1 | 100% |
+| 教程管理 | 4 | 4 | 100% |
+| AI分析 | 4 | 4 | 100% ✅ |
+| 配置管理 | 9 | 3 | 33% ⚠️(1个为内部接口) |
+
+**总体使用率约为 79%**(已实现ACM比赛辅助检查功能,竞赛管理使用率提升至75%)
+
+### 特殊说明
+
+#### 1. AI智能分析功能 ✨
+
+**功能概述**: 基于用户的学习数据,使用DeepSeek AI生成个性化的学习分析报告和建议。
+
+**涉及接口**:
+- `GET /api/ai/detail` - 获取详细学习数据
+- `GET /api/ai/duration` - 获取时段趋势数据
+- `GET /api/ai/heatmap` - 获取活跃度热力图
+- `POST /api/ai/analysis` - 生成AI分析(流式响应)
+
+**前端实现**:
+- **页面**: `src/oj/ai/analysis.vue`
+- **Store**: `src/oj/store/ai.ts`
+- **组件**:
+ - `DurationChart.vue` - 混合图表(柱状图+折线图),展示题目数、提交数、等级变化
+ - `Heatmap.vue` - 提交热力图
+ - `Details.vue` - 详细数据展示
+ - `AI.vue` - AI分析结果展示(Markdown格式)
+
+**时间范围选择**:
+支持多种时间范围:一节课(1小时)、两节课(2小时)、一天、一周、一个月、两个月、半年、一年
+
+**等级系统**:
+- **S级**: 排名前10%(卓越水平,约10%的人)
+- **A级**: 排名前35%(优秀水平,约25%的人)
+- **B级**: 排名前75%(良好水平,约40%的人)
+- **C级**: 75%之后(及格水平,约25%的人)
+- **特殊规则**: 参与人数少于10人时,S级降为A级,A级降为B级(避免因人少而评级虚高)
+
+**流式接口实现**:
+`POST /api/ai/analysis` 使用了**流式响应(Server-Sent Events)**,在 `oj/store/ai.ts` 中直接使用 `fetch` API调用,配合 `consumeJSONEventStream` 工具函数处理流式数据,实现AI内容的实时流式输出。
+
+**数据缓存**:
+为提升性能,后端对 `ai/detail` 和 `ai/duration` 接口的返回数据进行了缓存,相同参数的请求会直接返回缓存结果。
+
+#### 2. ACM 比赛辅助检查功能 ✨
+
+**功能说明**: 用于赛后人工审核 ACM 模式比赛的代码,检查是否存在抄袭、作弊等行为。
+
+**涉及接口**:
+- `GET /api/admin/contest/acm_helper` - 获取比赛中所有 AC 的提交记录
+ - **返回数据**: 用户名、题目ID、AC时间、错误次数、检查状态等
+- `PUT /api/admin/contest/acm_helper` - 更新提交的检查状态
+ - **参数**: contest_id, rank_id, problem_id, checked
+
+**使用场景**:
+1. 管理员进入比赛详情页,点击"审核"按钮进入辅助检查页面
+2. 系统展示所有 AC 提交的列表(按用户和题目分组)
+3. 管理员可以:
+ - 查看每个提交的代码详情
+ - 标记已检查的提交
+ - 批量标记所有提交为已检查
+ - 按用户名、题目、检查状态筛选
+4. 实时显示检查进度统计(总计/已检查/未检查)
+
+**实现位置**:
+- 页面: `src/admin/contest/helper.vue`
+- 路由: `/admin/contest/:contestID/helper`
+- API: `src/admin/api.ts` (getACMHelperList, updateACMHelperChecked)
+
+#### 3. 前端不需要的接口 ❌
+
+##### 3.1 数据冗余接口
+**`GET /api/submission_exists`** - 此接口**无需实现**
+
+**分析结论**:
+- 后端题目接口(`GET /api/problem`)已返回 `my_status` 字段
+- `my_status` 完整记录了用户的做题状态:
+ - `0` (JudgeStatus.ACCEPTED) = 已通过 ✅
+ - `-1` (JudgeStatus.WRONG_ANSWER) = 答案错误 ❌
+ - `-2` (JudgeStatus.COMPILE_ERROR) = 编译错误 ❌
+ - `1` (JudgeStatus.TIME_LIMIT_EXCEEDED) = 超时 ❌
+ - 其他非零值 = 其他失败原因 ❌
+ - `null/undefined` = 从未提交 ⭕
+
+**前端实现**:
+```typescript
+// 仅需一个计算属性即可判断
+const hasTriedButNotPassed = computed(() => {
+ return problem.value?.my_status !== undefined &&
+ problem.value?.my_status !== null &&
+ problem.value?.my_status !== 0
+})
+```
+
+**优势对比**:
+| 方案 | API请求 | 代码复杂度 | 性能 |
+|------|---------|-----------|------|
+| ❌ 使用 submission_exists | +1 | 高(需异步+状态管理) | 慢(额外网络请求) |
+| ✅ 使用 my_status | 0 | 低(3行计算属性) | 快(本地计算) |
+
+**经验教训**:
+- 实现新功能前应充分了解后端现有数据结构
+- 避免创建冗余接口,优先利用现有数据
+- 简单的解决方案往往是最好的
+
+---
+
+##### 3.2 内部系统接口
+**`POST /api/judge_server_heartbeat/`** - 此接口**无需前端实现**
+
+**接口说明**:
+- **用途**: 判题服务器(JudgeServer)向后端报告健康状态
+- **调用方**: JudgeServer Docker 容器(非前端)
+- **调用频率**: 每隔几秒自动调用一次
+- **认证方式**: 使用 `judge_server_token`(特殊令牌,非用户认证)
+
+**报告的数据**:
+```python
+{
+ "hostname": "judge-server-1",
+ "judger_version": "2.0.4",
+ "cpu_core": 4,
+ "cpu": 45.2, # CPU使用率
+ "memory": 60.5, # 内存使用率
+ "service_url": "http://judger:8080"
+}
+```
+
+**后端使用场景**:
+- 监控判题服务器在线状态
+- 显示服务器资源使用情况(仅在管理后台显示)
+- 负载均衡分配判题任务
+
+**前端已有接口**:
+前端查看判题服务器状态使用的是:
+- `GET /api/admin/judge_server` - 获取所有判题服务器列表及状态
+
+**结论**:
+此接口是系统内部通信接口,前端完全不需要调用。类似的内部接口还可能存在于分布式系统的其他服务间通信中。
+
+---
+
+## 六、新增功能模块
+
+### FlowchartEditor 流程图编辑器 ✨
+
+**功能概述**: 基于 Vue Flow 构建的完整流程图编辑器,支持拖拽创建、节点连接、编辑等核心功能。
+
+**技术实现**:
+- **核心库**: Vue Flow (@vue-flow/core)
+- **组件架构**: 模块化设计,9个独立功能模块
+- **状态管理**: 基于 Vue 3 Composition API
+- **数据持久化**: localStorage 自动缓存
+- **交互体验**: 拖拽、键盘快捷键、撤销重做
+
+**组件结构**:
+```
+FlowchartEditor/
+├── index.vue # 主组件 - 整合所有功能
+├── CustomNode.vue # 自定义节点 - 7种节点类型
+├── Toolbar.vue # 工具栏 - 节点创建和操作
+├── NodeHandles.vue # 节点操作手柄 - 连接点管理
+├── NodeActions.vue # 节点动作 - 删除、编辑按钮
+├── useCache.ts # 缓存管理 - 自动保存/恢复
+├── useDnD.ts # 拖拽处理 - 节点创建逻辑
+├── useFlowOperations.ts # 流程操作 - 增删改查
+├── useHistory.ts # 历史记录 - 撤销重做
+└── useNodeStyles.ts # 节点样式 - 类型配置
+```
+
+**节点类型支持**:
+- **开始节点** (start) - 流程开始,绿色主题
+- **输入节点** (input) - 数据输入,蓝色主题
+- **处理节点** (default) - 数据处理,紫色主题
+- **判断节点** (decision) - 条件判断,橙色主题
+- **循环节点** (loop) - 循环处理,青色主题
+- **输出节点** (output) - 数据输出,粉色主题
+- **结束节点** (end) - 流程结束,红色主题
+
+**核心功能**:
+1. **拖拽创建**: 从工具栏拖拽节点到画布
+2. **节点连接**: 支持节点间的连线操作
+3. **节点编辑**: 双击节点进行文本编辑
+4. **撤销重做**: 完整的历史记录管理
+5. **自动保存**: 本地缓存,防止数据丢失
+6. **键盘快捷键**: Ctrl+Z/Ctrl+Y 撤销重做,Delete 删除
+7. **批量操作**: 支持多选和批量删除
+
+**数据格式**:
+```typescript
+// 节点数据
+interface Node {
+ id: string
+ type: string
+ position: { x: number, y: number }
+ data: {
+ customLabel: string
+ [key: string]: any
+ }
+}
+
+// 边数据
+interface Edge {
+ id: string
+ source: string
+ target: string
+ sourceHandle?: string
+ targetHandle?: string
+ type?: string
+}
+```
+
+**使用场景**:
+- 算法流程图绘制
+- 业务流程设计
+- 教学演示工具
+- 问题分析图表
+
+**性能优化**:
+- 基于 Vue Flow 的高性能渲染
+- 模块化设计,按需加载
+- 本地缓存减少重复计算
+- 响应式状态管理
+
+**扩展性**:
+- 支持自定义节点类型
+- 可扩展工具栏功能
+- 支持主题定制
+- 支持数据导入导出
+
+**文档支持**:
+- 详细的使用文档: `docs/FlowchartEditor.md`
+- 完整的 API 说明
+- 最佳实践指南
+- 故障排除手册
+
diff --git a/apps/web/docs/FlowchartEditor.md b/apps/web/docs/FlowchartEditor.md
new file mode 100644
index 0000000..e026d3a
--- /dev/null
+++ b/apps/web/docs/FlowchartEditor.md
@@ -0,0 +1,311 @@
+# FlowchartEditor 流程图编辑器
+
+一个基于 Vue 3 + Vue Flow 构建的功能完整的流程图编辑器组件。
+
+## 功能特性
+
+### 🎯 核心功能
+- **拖拽创建节点** - 从工具栏拖拽节点到画布
+- **节点连接** - 支持节点间的连线操作
+- **节点编辑** - 双击节点进行文本编辑
+- **撤销重做** - 完整的历史记录管理
+- **自动保存** - 本地缓存,防止数据丢失
+- **键盘快捷键** - 支持常用快捷键操作
+
+### 🎨 节点类型
+- **开始节点** (start) - 流程开始
+- **输入节点** (input) - 数据输入
+- **处理节点** (default) - 数据处理
+- **判断节点** (decision) - 条件判断
+- **循环节点** (loop) - 循环处理
+- **输出节点** (output) - 数据输出
+- **结束节点** (end) - 流程结束
+
+### ⌨️ 快捷键
+- `Ctrl+Z` / `Cmd+Z` - 撤销
+- `Ctrl+Y` / `Cmd+Shift+Z` - 重做
+- `Delete` / `Backspace` - 删除选中节点
+
+## 组件结构
+
+```
+FlowchartEditor/
+├── index.vue # 主组件
+├── CustomNode.vue # 自定义节点组件
+├── Toolbar.vue # 工具栏组件
+├── NodeHandles.vue # 节点操作手柄
+├── NodeActions.vue # 节点动作按钮
+├── useCache.ts # 缓存管理
+├── useDnD.ts # 拖拽处理
+├── useFlowOperations.ts # 流程操作
+├── useHistory.ts # 历史记录
+└── useNodeStyles.ts # 节点样式
+```
+
+## 使用方法
+
+### 基本用法
+
+```vue
+
+
+ 将竞赛题目转为公开题目后,会创建一个新的公开题目副本,原题目保持不变。 +
+数据表
+{{ t.name }}
+期望结果
++ {{ t.dropped ? `${t.name} 表已被删除` : `执行后的 ${t.name} 表` }} +
+
+
+
+
+ + {{ data.question }} +
++ 点击你认为有错误的代码行(可多选)。 +
+ ++ {{ data.question }} +
+ ++ +
+ {{ data.question }} +
++ 把下面的项目拖到对应的分组里,可在分组间拖动调整。 +
+ ++ {{ bucket }} +
++ {{ data.question }} +
++ 先点左边一项,再点右边一项即可连线;点击已连线的项可取消。 +
+ ++ {{ data.question }} +
+ ++ {{ data.question }} +
+ +
+
+ 这段代码会输出什么?
++ {{ data.question }} +
+ +
+
+
+
+
{{ t.name }}
+
+
+ 结果顺序不限 +
+ ++ {{ t.dropped ? `${t.name} 表已被删除` : `执行后的 ${t.name} 表` }} +
+
+
+
+ {{ lang }} +
+
+
+
+
+ 共 {{ totalRows }} 行,仅展示前 {{ rows.length }} 行 +
+ + + diff --git a/apps/web/src/oj/problem/components/SubmissionResult.vue b/apps/web/src/oj/problem/components/SubmissionResult.vue new file mode 100644 index 0000000..cce77ba --- /dev/null +++ b/apps/web/src/oj/problem/components/SubmissionResult.vue @@ -0,0 +1,237 @@ + + + +({
+ value: "",
+ language: storage.get(STORAGE_KEY.LANGUAGE) || "Python3",
+ })
+
+ const input = ref("")
+ const output = ref("")
+
+ // ==================== 计算属性 ====================
+ const isEmpty = computed(() => code.value.trim() === "")
+
+ // ==================== 操作 ====================
+ /**
+ * 设置代码内容
+ */
+ function setCode(value: string) {
+ code.value = value
+ }
+
+ /**
+ * 设置编程语言
+ */
+ function setLanguage(language: LANGUAGE) {
+ code.language = language
+ storage.set(STORAGE_KEY.LANGUAGE, language)
+ }
+
+ /**
+ * 设置输入
+ */
+ function setInput(value: string) {
+ input.value = value
+ }
+
+ /**
+ * 设置输出
+ */
+ function setOutput(value: string) {
+ output.value = value
+ }
+
+ /**
+ * 重置所有状态
+ */
+ function reset() {
+ code.value = ""
+ input.value = ""
+ output.value = ""
+ }
+
+ /**
+ * 清空输出
+ */
+ function clearOutput() {
+ output.value = ""
+ }
+
+ // 监听语言变化,保存到本地存储
+ watch(
+ () => code.language,
+ (newLanguage) => {
+ storage.set(STORAGE_KEY.LANGUAGE, newLanguage)
+ },
+ )
+
+ return {
+ // 状态
+ code,
+ input,
+ output,
+
+ // 计算属性
+ isEmpty,
+
+ // 操作
+ setCode,
+ setLanguage,
+ setInput,
+ setOutput,
+ reset,
+ clearOutput,
+ }
+})
diff --git a/apps/web/src/oj/store/contest.ts b/apps/web/src/oj/store/contest.ts
new file mode 100644
index 0000000..bd58561
--- /dev/null
+++ b/apps/web/src/oj/store/contest.ts
@@ -0,0 +1,116 @@
+import { formatISO, getTime, parseISO } from "date-fns"
+import { useUserStore } from "shared/store/user"
+import { ContestStatus, ContestType } from "utils/constants"
+import { duration } from "utils/functions"
+import type { Contest, Problem } from "utils/types"
+import {
+ checkContestPassword,
+ getContest,
+ getContestAccess,
+ getContestProblems,
+} from "../api"
+
+export const useContestStore = defineStore("contest", () => {
+ const userStore = useUserStore()
+ const [access, toggleAccess] = useToggle(false)
+ const contest = ref(null)
+ const problems = ref([])
+ const now = ref(0)
+
+ let timer = 0
+
+ const contestStatus = computed(() => {
+ if (!contest.value) return ContestStatus.initial
+ const start = getTime(parseISO(contest.value.start_time.toString()))
+ const end = getTime(parseISO(contest.value.end_time.toString()))
+ if (start > now.value) {
+ return ContestStatus.not_started
+ } else if (end < now.value) {
+ return ContestStatus.finished
+ } else {
+ return ContestStatus.underway
+ }
+ })
+
+ const countdown = computed(() => {
+ if (contestStatus.value === ContestStatus.finished) {
+ return "已结束"
+ } else if (contestStatus.value === ContestStatus.not_started) {
+ const d = duration(formatISO(now.value), contest.value!.start_time, true)
+ return "距离比赛开始 " + d
+ } else {
+ const d = duration(formatISO(now.value), contest.value!.end_time, true)
+ return "距离比赛结束 " + d
+ }
+ })
+
+ const isContestAdmin = computed(
+ () =>
+ userStore.isSuperAdmin ||
+ (userStore.isAuthed &&
+ contest.value?.created_by.id === userStore.user!.id),
+ )
+
+ const isPrivate = computed(
+ () => contest.value!.contest_type === ContestType.private,
+ )
+
+ async function init(contestID: string) {
+ problems.value = []
+ const res = await getContest(contestID)
+ contest.value = res.data
+ now.value = getTime(parseISO(res.data.now))
+ if (contestStatus.value !== ContestStatus.finished) {
+ timer = setInterval(() => {
+ now.value = now.value + 1000
+ }, 1000)
+ }
+ if (contest.value?.contest_type === ContestType.private) {
+ const res = await getContestAccess(contestID)
+ toggleAccess(res.data.access)
+ }
+ _getProblems(contestID)
+ }
+
+ function clear() {
+ contest.value = null
+ problems.value = []
+ toggleAccess(false)
+ now.value = 0
+ if (timer) clearInterval(timer)
+ }
+
+ async function checkPassword(contestID: string, password: string) {
+ try {
+ const res = await checkContestPassword(contestID, password)
+ toggleAccess(res.data)
+ if (res.data) {
+ _getProblems(contestID)
+ }
+ } catch (err) {
+ toggleAccess(false)
+ }
+ }
+
+ async function _getProblems(contestID: string) {
+ try {
+ problems.value = await getContestProblems(contestID)
+ } catch (err) {
+ problems.value = []
+ toggleAccess(false)
+ }
+ }
+
+ return {
+ contest,
+ contestStatus,
+ isContestAdmin,
+ access,
+ problems,
+ isPrivate,
+ countdown,
+ init,
+ clear,
+ checkPassword,
+ }
+})
diff --git a/apps/web/src/oj/store/problem.ts b/apps/web/src/oj/store/problem.ts
new file mode 100644
index 0000000..ead60ec
--- /dev/null
+++ b/apps/web/src/oj/store/problem.ts
@@ -0,0 +1,34 @@
+import { defineStore } from "pinia"
+import type { LANGUAGE, Problem } from "utils/types"
+
+export const useProblemStore = defineStore("problem", () => {
+ const problem = ref(null)
+ const route = useRoute()
+
+ const failCount = ref(0)
+
+ const languages = computed(() => {
+ if (route.name === "problem" && problem.value?.allow_flowchart) {
+ return ["Flowchart", ...problem.value?.languages]
+ }
+ return problem.value?.languages ?? []
+ })
+
+ function incrementFailCount() {
+ failCount.value++
+ }
+
+ watch(
+ () => problem.value?.id,
+ () => {
+ failCount.value = 0
+ },
+ )
+
+ return {
+ problem,
+ failCount,
+ languages,
+ incrementFailCount,
+ }
+})
diff --git a/apps/web/src/oj/submission/components/ButtonWithSearch.vue b/apps/web/src/oj/submission/components/ButtonWithSearch.vue
new file mode 100644
index 0000000..6e70507
--- /dev/null
+++ b/apps/web/src/oj/submission/components/ButtonWithSearch.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ "搜索" + props.type }}
+
+
+
+
+
+
+
+
+
+ 筛选班级
+
+
+
diff --git a/apps/web/src/oj/submission/components/FlowchartLink.vue b/apps/web/src/oj/submission/components/FlowchartLink.vue
new file mode 100644
index 0000000..34a1cdb
--- /dev/null
+++ b/apps/web/src/oj/submission/components/FlowchartLink.vue
@@ -0,0 +1,35 @@
+
+
+ {{ flowchart.id.slice(0, 12) }}
+
+
+ {{ flowchart.id.slice(0, 12) }}
+
+
+
+
+
diff --git a/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue b/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue
new file mode 100644
index 0000000..9303ae4
--- /dev/null
+++ b/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue
@@ -0,0 +1,226 @@
+
+
+
+
+
+
+
+
+
+
+ 查看大图
+
+
+
+
+ {{ renderError }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ submission.ai_feedback }}
+
+
+
+
+
+
+ {{ suggestion }}
+
+
+
+
+
+
+
+
+
+ {{ key }}
+
+ {{ detail.score || 0 }}分 / {{ detail.max }}分
+
+
+
+
+ {{ detail.comment }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/oj/submission/components/Grade.vue b/apps/web/src/oj/submission/components/Grade.vue
new file mode 100644
index 0000000..3e89ad1
--- /dev/null
+++ b/apps/web/src/oj/submission/components/Grade.vue
@@ -0,0 +1,25 @@
+
+
+ {{ score }}
+ ({{ grade }})
+
+
+
+
diff --git a/apps/web/src/oj/submission/components/SubmissionLink.vue b/apps/web/src/oj/submission/components/SubmissionLink.vue
new file mode 100644
index 0000000..ced3704
--- /dev/null
+++ b/apps/web/src/oj/submission/components/SubmissionLink.vue
@@ -0,0 +1,52 @@
+
+
+
+ {{ props.submission.id.slice(0, 12) }}
+
+
+
+
+
+
+
+
+
+ 查看测试详情
+
+
+
+ {{ props.submission.id.slice(0, 12) }}
+
+
+
+
+
+
+
+
+ 这道题在你已经加入的题单中,只有在题单中完成此题,代码才可见。
+
+
+ {{ props.submission.id.slice(0, 12) }}
+
+
+
diff --git a/apps/web/src/oj/submission/detail.vue b/apps/web/src/oj/submission/detail.vue
new file mode 100644
index 0000000..db7ef00
--- /dev/null
+++ b/apps/web/src/oj/submission/detail.vue
@@ -0,0 +1,168 @@
+
+
+
+
+
+
+
+ 提交时间:{{ parseTime(submission.create_time) }}
+ 编程语言:{{ LANGUAGE_SHOW_VALUE[submission.language] }}
+ 用户:{{ submission.username }}
+
+
+
+
+ 复制到自测猫
+
+ 复制回到题目
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/oj/submission/list.vue b/apps/web/src/oj/submission/list.vue
new file mode 100644
index 0000000..d15f77d
--- /dev/null
+++ b/apps/web/src/oj/submission/list.vue
@@ -0,0 +1,573 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+
+ 重置
+
+
+
+ 数据统计
+
+
+
+ (query.today = v ? '1' : '0')"
+ >
+
+ 今日提交数:{{ todayCount }}
+
+ 今日提交数:{{ todayCount }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 流程图评分详情
+
+ {{ selectedFlowchart.ai_score }}分 {{ selectedFlowchart.ai_grade }}级
+
+
+
+
+
+
+
diff --git a/apps/web/src/oj/transforms.ts b/apps/web/src/oj/transforms.ts
new file mode 100644
index 0000000..80f8ac7
--- /dev/null
+++ b/apps/web/src/oj/transforms.ts
@@ -0,0 +1,29 @@
+import { DIFFICULTY } from "utils/constants"
+import { getACRate } from "utils/functions"
+import type { Problem } from "utils/types"
+
+// 把后端的 Problem 塑形成列表项需要的形状,与请求逻辑解耦。
+export function filterResult(result: Problem) {
+ const newResult = {
+ id: result.id,
+ _id: result._id,
+ title: result.title,
+ difficulty: DIFFICULTY[result.difficulty],
+ tags: result.tags,
+ submission: result.submission_number,
+ rate: getACRate(result.accepted_number, result.submission_number),
+ status: "",
+ author: result.created_by.username,
+ allow_flowchart: result.allow_flowchart,
+ show_flowchart: result.show_flowchart,
+ has_ast_rules: result.has_ast_rules,
+ }
+ if (result.my_status === null || result.my_status === undefined) {
+ newResult.status = "not_test"
+ } else if (result.my_status === 0) {
+ newResult.status = "passed"
+ } else {
+ newResult.status = "failed"
+ }
+ return newResult
+}
diff --git a/apps/web/src/oj/user/index.vue b/apps/web/src/oj/user/index.vue
new file mode 100644
index 0000000..6898961
--- /dev/null
+++ b/apps/web/src/oj/user/index.vue
@@ -0,0 +1,331 @@
+
+
+
+
+ {{ profile.user.username }}
+ {{ profile.mood }}
+
+ 智能分析
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.title }}
+
+
+ {{ item.content }}
+
+
+
+
+
+
+
+
+
+
+
+ 成就 {{ achievementSummary.unlocked }} /
+ {{ achievementSummary.total }}
+
+
+ {{ achievementSummary.percent }}%
+
+
+
+ 查看全部
+
+
+
+
+ 最近获得
+
+
+
+
+
+ {{ a.name }}
+
+
+ 还没有获得成就
+
+
+
+
+
+
+
+
+ 已解决的题目
+
+ {{ show ? "隐藏全部" : "显示全部" }}
+
+
+
+
+
+
+ {{ id }}
+
+
+
+
+
+
+
+ 返回主页
+
+
+
+
diff --git a/apps/web/src/oj/user/message.vue b/apps/web/src/oj/user/message.vue
new file mode 100644
index 0000000..02997b6
--- /dev/null
+++ b/apps/web/src/oj/user/message.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+ 发送时间
+ {{ parseTime(item.create_time, "YYYY年M月D日 HH:mm:ss") }}
+ 发送者
+ {{ item.sender.username }}
+
+
+ 题目序号
+
+ {{ item.submission.problem }}
+
+
+ {{ JUDGE_STATUS[item.submission.result]["name"] }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/oj/user/setting.vue b/apps/web/src/oj/user/setting.vue
new file mode 100644
index 0000000..06be145
--- /dev/null
+++ b/apps/web/src/oj/user/setting.vue
@@ -0,0 +1,76 @@
+
+
+
+ 个人信息设置
+
+
+
+
+ 上传头像
+
+
+
+
+
+
+ 更改信息
+
+
+
+
diff --git a/apps/web/src/routes.ts b/apps/web/src/routes.ts
new file mode 100644
index 0000000..b815626
--- /dev/null
+++ b/apps/web/src/routes.ts
@@ -0,0 +1,339 @@
+import type { RouteRecordRaw } from "vue-router"
+
+export const ojs: RouteRecordRaw = {
+ path: "/",
+ component: () => import("shared/layout/default.vue"),
+ children: [
+ { path: "", component: () => import("oj/problem/list.vue") },
+ {
+ path: "problem/:problemID",
+ component: () => import("oj/problem/detail.vue"),
+ props: true,
+ name: "problem",
+ },
+ {
+ path: "submission",
+ component: () => import("oj/submission/list.vue"),
+ name: "submissions",
+ },
+ {
+ path: "submission/:submissionID",
+ component: () => import("oj/submission/detail.vue"),
+ props: true,
+ meta: { requiresAuth: true },
+ },
+ {
+ path: "contest",
+ component: () => import("oj/contest/list.vue"),
+ name: "contests",
+ },
+ {
+ path: "contest/:contestID",
+ component: () => import("oj/contest/detail.vue"),
+ props: true,
+ meta: { requiresAuth: true },
+ children: [
+ {
+ path: "",
+ component: () => import("oj/contest/pages/problems.vue"),
+ props: true,
+ meta: { requiresAuth: true },
+ name: "contest problems",
+ },
+ {
+ path: "submission",
+ component: () => import("oj/submission/list.vue"),
+ meta: { requiresAuth: true },
+ name: "contest submissions",
+ },
+ {
+ path: "rank",
+ component: () => import("oj/contest/pages/rank.vue"),
+ props: true,
+ meta: { requiresAuth: true },
+ name: "contest rank",
+ },
+ ],
+ },
+ {
+ path: "contest/:contestID/problem/:problemID",
+ component: () => import("oj/problem/detail.vue"),
+ props: true,
+ name: "contest problem",
+ meta: { requiresAuth: true },
+ },
+ {
+ path: "rank",
+ name: "rank",
+ component: () => import("oj/rank/list.vue"),
+ },
+ {
+ path: "class",
+ name: "class",
+ component: () => import("oj/class/pk.vue"),
+ },
+ {
+ path: "announcement",
+ name: "announcements",
+ component: () => import("oj/announcement/list.vue"),
+ },
+ {
+ path: "user",
+ component: () => import("oj/user/index.vue"),
+ meta: { requiresAuth: true },
+ },
+ {
+ path: "achievement",
+ name: "achievement",
+ component: () => import("oj/achievement/index.vue"),
+ meta: { requiresAuth: true },
+ },
+ {
+ path: "setting",
+ component: () => import("oj/user/setting.vue"),
+ meta: { requiresAuth: true },
+ },
+ {
+ path: "message",
+ component: () => import("oj/user/message.vue"),
+ meta: { requiresAuth: true },
+ },
+ {
+ path: "learn/:type/:step",
+ name: "learn",
+ component: () => import("oj/learn/index.vue"),
+ props: true,
+ },
+ {
+ path: "ai-analysis",
+ name: "ai",
+ component: () => import("oj/ai/analysis.vue"),
+ meta: { requiresAuth: true },
+ },
+ {
+ path: "flowchart",
+ name: "flowchart",
+ component: () => import("oj/flowchart/index.vue"),
+ },
+ {
+ path: "problemset",
+ component: () => import("oj/problemset/list.vue"),
+ name: "problemsets",
+ },
+ {
+ path: "problemset/:problemSetId",
+ component: () => import("oj/problemset/detail.vue"),
+ props: true,
+ name: "problemset",
+ },
+ {
+ path: "problemset/:problemSetId/problem/:problemID",
+ component: () => import("oj/problem/detail.vue"),
+ props: true,
+ name: "problemset problem",
+ meta: { requiresAuth: true },
+ },
+ ],
+}
+
+export const admins: RouteRecordRaw = {
+ path: "/admin",
+ component: () => import("shared/layout/admin.vue"),
+ children: [
+ {
+ path: "",
+ name: "admin home",
+ component: () => import("admin/setting/home.vue"),
+ meta: { requiresSuperAdmin: true },
+ },
+ {
+ path: "config",
+ name: "admin config",
+ component: () => import("admin/setting/config.vue"),
+ meta: { requiresSuperAdmin: true },
+ },
+ {
+ path: "user/list",
+ name: "admin user list",
+ component: () => import("admin/user/list.vue"),
+ meta: { requiresSuperAdmin: true },
+ },
+ {
+ path: "user/generate",
+ name: "admin user generate",
+ component: () => import("admin/user/generate.vue"),
+ meta: { requiresSuperAdmin: true },
+ },
+ // admin和super_admin都可以访问的路由 (需要题目权限)
+ {
+ path: "problem/list",
+ name: "admin problem list",
+ component: () => import("admin/problem/list.vue"),
+ meta: { requiresProblemPermission: true },
+ },
+ {
+ path: "problem/tags",
+ name: "admin tag list",
+ component: () => import("admin/problem/tags.vue"),
+ meta: { requiresProblemPermission: true },
+ },
+ {
+ path: "problem/create",
+ name: "admin problem create",
+ component: () => import("admin/problem/detail.vue"),
+ meta: { requiresProblemPermission: true },
+ },
+ {
+ path: "problem/edit/:problemID",
+ name: "admin problem edit",
+ component: () => import("admin/problem/detail.vue"),
+ props: true,
+ meta: { requiresProblemPermission: true },
+ },
+ {
+ path: "contest/list",
+ name: "admin contest list",
+ component: () => import("admin/contest/list.vue"),
+ meta: { requiresTeacherAdmin: true },
+ },
+ {
+ path: "contest/create",
+ name: "admin contest create",
+ component: () => import("admin/contest/detail.vue"),
+ meta: { requiresTeacherAdmin: true },
+ },
+ {
+ path: "contest/edit/:contestID",
+ name: "admin contest edit",
+ component: () => import("admin/contest/detail.vue"),
+ props: true,
+ meta: { requiresTeacherAdmin: true },
+ },
+ {
+ path: "contest/:contestID/problem/list",
+ name: "admin contest problem list",
+ component: () => import("admin/problem/list.vue"),
+ props: true,
+ meta: { requiresTeacherAdmin: true },
+ },
+ {
+ path: "contest/:contestID/problem/create",
+ name: "admin contest problem create",
+ component: () => import("admin/problem/detail.vue"),
+ props: true,
+ meta: { requiresTeacherAdmin: true },
+ },
+ {
+ path: "contest/:contestID/problem/edit/:problemID",
+ name: "admin contest problem edit",
+ component: () => import("admin/problem/detail.vue"),
+ props: true,
+ meta: { requiresTeacherAdmin: true },
+ },
+ {
+ path: "contest/:contestID/helper",
+ name: "admin contest helper",
+ component: () => import("admin/contest/helper.vue"),
+ props: true,
+ meta: { requiresTeacherAdmin: true },
+ },
+ // 只有super_admin可以访问的路由
+ {
+ path: "announcement/list",
+ name: "admin announcement list",
+ component: () => import("admin/announcement/list.vue"),
+ meta: { requiresSuperAdmin: true },
+ },
+ {
+ path: "announcement/create",
+ name: "admin announcement create",
+ component: () => import("admin/announcement/detail.vue"),
+ meta: { requiresSuperAdmin: true },
+ },
+ {
+ path: "announcement/edit/:announcementID",
+ name: "admin announcement edit",
+ component: () => import("admin/announcement/detail.vue"),
+ props: true,
+ meta: { requiresSuperAdmin: true },
+ },
+ {
+ path: "message/list",
+ name: "admin message list",
+ component: () => import("admin/communication/messages.vue"),
+ meta: { requiresSuperAdmin: true },
+ },
+ {
+ path: "tutorial/list",
+ name: "admin tutorial list",
+ component: () => import("admin/tutorial/list.vue"),
+ meta: { requiresSuperAdmin: true },
+ },
+ {
+ path: "tutorial/create",
+ name: "admin tutorial create",
+ component: () => import("admin/tutorial/detail.vue"),
+ meta: { requiresSuperAdmin: true },
+ },
+ {
+ path: "tutorial/edit/:tutorialID",
+ name: "admin tutorial edit",
+ component: () => import("admin/tutorial/detail.vue"),
+ props: true,
+ meta: { requiresSuperAdmin: true },
+ },
+ {
+ path: "problem/stuck",
+ name: "admin stuck problems",
+ component: () => import("admin/problem/Stuck.vue"),
+ meta: { requiresTeacherAdmin: true },
+ },
+ {
+ path: "problem/top_ac_trend",
+ name: "admin top ac trend",
+ component: () => import("admin/problem/TopACTrend.vue"),
+ meta: { requiresTeacherAdmin: true },
+ },
+ // 题单管理路由
+ {
+ path: "problemset/list",
+ name: "admin problemset list",
+ component: () => import("admin/problemset/list.vue"),
+ meta: { requiresTeacherAdmin: true },
+ },
+ {
+ path: "achievement/list",
+ name: "admin achievement list",
+ // 后端 admin 接口是 super_admin_required,这里必须跟着用
+ // requiresSuperAdmin,否则教师能进页面但每个请求都 403
+ component: () => import("admin/achievement/list.vue"),
+ meta: { requiresSuperAdmin: true },
+ },
+ {
+ path: "problemset/create",
+ name: "admin problemset create",
+ component: () => import("admin/problemset/edit.vue"),
+ meta: { requiresTeacherAdmin: true },
+ },
+ {
+ path: "problemset/edit/:problemSetId",
+ name: "admin problemset edit",
+ component: () => import("admin/problemset/edit.vue"),
+ props: true,
+ meta: { requiresTeacherAdmin: true },
+ },
+ {
+ path: "problemset/:problemSetId",
+ name: "admin problemset detail",
+ component: () => import("admin/problemset/detail.vue"),
+ props: true,
+ meta: { requiresTeacherAdmin: true },
+ },
+ {
+ path: "ai/reports",
+ name: "admin ai reports",
+ component: () => import("admin/ai/list.vue"),
+ meta: { requiresTeacherAdmin: true },
+ },
+ ],
+}
diff --git a/apps/web/src/shared/api.ts b/apps/web/src/shared/api.ts
new file mode 100644
index 0000000..939a4cb
--- /dev/null
+++ b/apps/web/src/shared/api.ts
@@ -0,0 +1,34 @@
+import http from "utils/http"
+import type { Profile, Tag } from "utils/types"
+
+export function login(data: { username: string; password: string }) {
+ return http.post("login", data)
+}
+
+export function signup(data: {
+ username: string
+ email: string
+ password: string
+}) {
+ return http.post("register", data)
+}
+
+export function logout() {
+ return http.get("logout")
+}
+
+export function getProfile(username: string = "") {
+ return http.get("profile", { params: { username } })
+}
+
+export function getProblemTagList() {
+ return http.get("problem/tags")
+}
+
+export function getHitokoto() {
+ return http.get("hitokoto")
+}
+
+export function getClassUsernames(classroom: string) {
+ return http.get("class_usernames", { params: { classroom: classroom } })
+}
diff --git a/apps/web/src/shared/components/AchievementIcon.vue b/apps/web/src/shared/components/AchievementIcon.vue
new file mode 100644
index 0000000..b8fb970
--- /dev/null
+++ b/apps/web/src/shared/components/AchievementIcon.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+ {{ icon }}
+
+
+
+
diff --git a/apps/web/src/shared/components/AchievementToast.vue b/apps/web/src/shared/components/AchievementToast.vue
new file mode 100644
index 0000000..1d83dc0
--- /dev/null
+++ b/apps/web/src/shared/components/AchievementToast.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+ {{ current.kind === "badge" ? "获得奖章" : "成就解锁" }}
+
+ {{ current.name }}
+ {{ current.description }}
+
+
+
+
+
+
diff --git a/apps/web/src/shared/components/AuthorSelect.vue b/apps/web/src/shared/components/AuthorSelect.vue
new file mode 100644
index 0000000..e2b2345
--- /dev/null
+++ b/apps/web/src/shared/components/AuthorSelect.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
diff --git a/apps/web/src/shared/components/Beian.vue b/apps/web/src/shared/components/Beian.vue
new file mode 100644
index 0000000..3b1aa0a
--- /dev/null
+++ b/apps/web/src/shared/components/Beian.vue
@@ -0,0 +1,69 @@
+
+
+
+ {{ copyrightText }}
+ CC BY-NC 4.0
+
+
+ 浙ICP备2023044109号-1
+
+ 浙公网安备33100402331786号
+
+
+
+
+
+
diff --git a/apps/web/src/shared/components/CodeEditor.vue b/apps/web/src/shared/components/CodeEditor.vue
new file mode 100644
index 0000000..91ab92d
--- /dev/null
+++ b/apps/web/src/shared/components/CodeEditor.vue
@@ -0,0 +1,65 @@
+
+
+
+
+
diff --git a/apps/web/src/shared/components/ContestTitle.vue b/apps/web/src/shared/components/ContestTitle.vue
new file mode 100644
index 0000000..ba70df9
--- /dev/null
+++ b/apps/web/src/shared/components/ContestTitle.vue
@@ -0,0 +1,18 @@
+
+
+
+
+ {{ contest.title }}
+
+
+
diff --git a/apps/web/src/shared/components/ContestType.vue b/apps/web/src/shared/components/ContestType.vue
new file mode 100644
index 0000000..c2a98dc
--- /dev/null
+++ b/apps/web/src/shared/components/ContestType.vue
@@ -0,0 +1,21 @@
+
+
+
+
+ {{ isPrivate ? "需要密码" : "公开" }}
+
+
diff --git a/apps/web/src/shared/components/Copy.vue b/apps/web/src/shared/components/Copy.vue
new file mode 100644
index 0000000..e83330d
--- /dev/null
+++ b/apps/web/src/shared/components/Copy.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+ {{ copied ? "已复制" : "复制" }}
+
+
+
diff --git a/apps/web/src/shared/components/FlowchartEditor/CustomNode.vue b/apps/web/src/shared/components/FlowchartEditor/CustomNode.vue
new file mode 100644
index 0000000..8cd3cf1
--- /dev/null
+++ b/apps/web/src/shared/components/FlowchartEditor/CustomNode.vue
@@ -0,0 +1,275 @@
+
+
+
+
+
+
+
+
+ {{ displayLabel }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/shared/components/FlowchartEditor/NodeActions.vue b/apps/web/src/shared/components/FlowchartEditor/NodeActions.vue
new file mode 100644
index 0000000..c2695cc
--- /dev/null
+++ b/apps/web/src/shared/components/FlowchartEditor/NodeActions.vue
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/shared/components/FlowchartEditor/NodeHandles.vue b/apps/web/src/shared/components/FlowchartEditor/NodeHandles.vue
new file mode 100644
index 0000000..07e9977
--- /dev/null
+++ b/apps/web/src/shared/components/FlowchartEditor/NodeHandles.vue
@@ -0,0 +1,238 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 是
+ 否
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 进入
+ 返回
+ 继续
+ 退出
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/shared/components/FlowchartEditor/Toolbar.vue b/apps/web/src/shared/components/FlowchartEditor/Toolbar.vue
new file mode 100644
index 0000000..df95c7e
--- /dev/null
+++ b/apps/web/src/shared/components/FlowchartEditor/Toolbar.vue
@@ -0,0 +1,413 @@
+
+
+
+
+
+
diff --git a/apps/web/src/shared/components/FlowchartEditor/index.vue b/apps/web/src/shared/components/FlowchartEditor/index.vue
new file mode 100644
index 0000000..c4a665a
--- /dev/null
+++ b/apps/web/src/shared/components/FlowchartEditor/index.vue
@@ -0,0 +1,325 @@
+
+
+
+
+
+
+
+
+ {{ getNodeTypeConfig(currentDragNodeType).label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/shared/components/FlowchartEditor/useCache.ts b/apps/web/src/shared/components/FlowchartEditor/useCache.ts
new file mode 100644
index 0000000..bc332fd
--- /dev/null
+++ b/apps/web/src/shared/components/FlowchartEditor/useCache.ts
@@ -0,0 +1,89 @@
+import { ref, watch, type Ref, type MaybeRefOrGetter } from "vue"
+import { useStorage, useDebounceFn } from "@vueuse/core"
+import type { Node, Edge } from "@vue-flow/core"
+
+/**
+ * 缓存管理 - 使用 @vueuse 的 useStorage
+ */
+export function useCache(
+ nodes: Ref,
+ edges: Ref,
+ storageKey: MaybeRefOrGetter = "flowchart-editor-data",
+) {
+ const isSaving = ref(false)
+ const lastSaved = ref(null)
+ const hasUnsavedChanges = ref(false)
+
+ // 使用 useStorage 管理数据存储,支持响应式 key(题目 ID 异步加载时自动切换)
+ const storedData = useStorage<{
+ nodes: Node[]
+ edges: Edge[]
+ timestamp: string
+ }>(storageKey, {
+ nodes: [],
+ edges: [],
+ timestamp: "",
+ })
+
+ // 防抖保存:isSaving 在 watch 中置 true,保存完成后置 false,使 UI 能感知保存中状态
+ const debouncedSave = useDebounceFn(() => {
+ storedData.value.nodes = nodes.value
+ storedData.value.edges = edges.value
+ storedData.value.timestamp = new Date().toISOString()
+ lastSaved.value = new Date()
+ hasUnsavedChanges.value = false
+ isSaving.value = false
+ }, 500)
+
+ // 立即保存
+ const saveToCache = () => {
+ isSaving.value = true
+ storedData.value.nodes = nodes.value
+ storedData.value.edges = edges.value
+ storedData.value.timestamp = new Date().toISOString()
+ lastSaved.value = new Date()
+ hasUnsavedChanges.value = false
+ isSaving.value = false
+ }
+
+ // 从缓存加载数据
+ const loadFromCache = () => {
+ if (storedData.value.nodes?.length || storedData.value.edges?.length) {
+ nodes.value = storedData.value.nodes
+ edges.value = storedData.value.edges
+ lastSaved.value = storedData.value.timestamp
+ ? new Date(storedData.value.timestamp)
+ : null
+ hasUnsavedChanges.value = false
+ return true
+ }
+ return false
+ }
+
+ // 清除缓存数据
+ const clearCache = () => {
+ storedData.value = { nodes: [], edges: [], timestamp: "" }
+ lastSaved.value = null
+ hasUnsavedChanges.value = false
+ }
+
+ // 监听节点和边的变化,isSaving 在此置 true 以覆盖防抖等待窗口
+ watch(
+ [nodes, edges],
+ () => {
+ hasUnsavedChanges.value = true
+ isSaving.value = true
+ debouncedSave()
+ },
+ { deep: true },
+ )
+
+ return {
+ isSaving,
+ lastSaved,
+ hasUnsavedChanges,
+ saveToCache,
+ loadFromCache,
+ clearCache,
+ }
+}
diff --git a/apps/web/src/shared/components/FlowchartEditor/useDnD.ts b/apps/web/src/shared/components/FlowchartEditor/useDnD.ts
new file mode 100644
index 0000000..da6b761
--- /dev/null
+++ b/apps/web/src/shared/components/FlowchartEditor/useDnD.ts
@@ -0,0 +1,84 @@
+import { ref } from "vue"
+import { useVueFlow } from "@vue-flow/core"
+import {
+ getNodeTypeConfig,
+ createNodeStyle,
+ getNodeDimensions,
+} from "./useNodeStyles"
+import { getRandomId } from "utils/functions"
+
+// 模块级共享:当前拖拽的节点类型(Toolbar 写入,canvas 读取)
+export const currentDragNodeType = ref(null)
+
+/**
+ * 简化的拖拽处理
+ */
+export function useDnD() {
+ const { addNodes, screenToFlowCoordinate } = useVueFlow()
+ const isDragOver = ref(false)
+ const screenDragPos = ref<{ x: number; y: number } | null>(null)
+
+ // 拖拽悬停处理
+ const onDragOver = (event: DragEvent) => {
+ event.preventDefault()
+ isDragOver.value = true
+ screenDragPos.value = { x: event.clientX, y: event.clientY }
+ }
+
+ // 拖拽离开处理
+ const onDragLeave = () => {
+ isDragOver.value = false
+ screenDragPos.value = null
+ }
+
+ // 拖拽放置处理
+ const onDrop = (event: DragEvent) => {
+ event.preventDefault()
+ isDragOver.value = false
+ screenDragPos.value = null
+ currentDragNodeType.value = null
+
+ const type = event.dataTransfer?.getData("application/vueflow")
+ if (!type) return
+
+ // 获取鼠标在画布中的坐标
+ const position = screenToFlowCoordinate({
+ x: event.clientX,
+ y: event.clientY,
+ })
+
+ // 根据节点类型获取实际尺寸
+ const dimensions = getNodeDimensions(type)
+
+ // 调整位置,使节点中心点对齐到鼠标位置
+ const adjustedPosition = {
+ x: position.x - dimensions.width / 2,
+ y: position.y - dimensions.height / 2,
+ }
+
+ const nodeId = `node-${getRandomId()}`
+ const config = getNodeTypeConfig(type)
+ const newNode = {
+ id: nodeId,
+ type: "custom",
+ position: adjustedPosition,
+ data: {
+ label: config.label,
+ color: config.color,
+ originalType: type,
+ },
+ style: createNodeStyle(type),
+ }
+
+ addNodes([newNode])
+ return newNode
+ }
+
+ return {
+ isDragOver,
+ screenDragPos,
+ onDragOver,
+ onDragLeave,
+ onDrop,
+ }
+}
diff --git a/apps/web/src/shared/components/FlowchartEditor/useFlowOperations.ts b/apps/web/src/shared/components/FlowchartEditor/useFlowOperations.ts
new file mode 100644
index 0000000..e9d41d4
--- /dev/null
+++ b/apps/web/src/shared/components/FlowchartEditor/useFlowOperations.ts
@@ -0,0 +1,142 @@
+import type { Ref } from "vue"
+import type { Node, Edge, Connection } from "@vue-flow/core"
+import { useVueFlow } from "@vue-flow/core"
+import { getRandomId } from "utils/functions"
+
+export function useFlowOperations(
+ nodes: Ref,
+ edges: Ref,
+ addEdges: (edges: Edge[]) => void,
+ removeNodes: (nodeIds: string[]) => void,
+ removeEdges: (edgeIds: string[]) => void,
+ saveState: (nodes: Node[], edges: Edge[]) => void,
+) {
+ const { findNode, getSelectedNodes, getSelectedEdges } = useVueFlow()
+ const getAutoLabel = (
+ sourceNode: Node | undefined,
+ targetNode: Node | undefined,
+ sourceHandle: string | null | undefined,
+ targetHandle: string | null | undefined,
+ ) => {
+ const sourceType = sourceNode?.data?.originalType || sourceNode?.type
+ const targetType = targetNode?.data?.originalType || targetNode?.type
+
+ // 如果是判断节点
+ if (sourceType === "decision") {
+ // 根据handle ID推断标签
+ if (sourceHandle === "yes") {
+ return "是"
+ } else if (sourceHandle === "no") {
+ return "否"
+ }
+ }
+
+ // 如果是循环节点
+ if (sourceType === "loop") {
+ // 根据handle ID推断标签
+ if (sourceHandle === "continue") {
+ return "继续"
+ } else if (sourceHandle === "exit") {
+ return "退出"
+ }
+ }
+
+ // 如果是循环体回到循环节点
+ if (targetType === "loop") {
+ if (targetHandle === "return") {
+ return "返回"
+ }
+ }
+ // 默认情况
+ return ""
+ }
+
+ const handleConnect = (params: Connection) => {
+ const sourceNode = nodes.value.find((node) => node.id === params.source)
+ const targetNode = nodes.value.find((node) => node.id === params.target)
+
+ // 自动推断标签
+ const autoLabel = getAutoLabel(
+ sourceNode,
+ targetNode,
+ params.sourceHandle,
+ params.targetHandle,
+ )
+
+ const newEdge: Edge = {
+ id: `edge-${getRandomId()}`,
+ source: params.source,
+ target: params.target,
+ sourceHandle: params.sourceHandle,
+ targetHandle: params.targetHandle,
+ type: "default",
+ label: autoLabel,
+ }
+
+ addEdges([newEdge])
+ saveState(nodes.value, edges.value)
+ }
+
+ const handleEdgeClick = ({ edge }: { edge: Edge }) => {
+ removeEdges([edge.id])
+ saveState(nodes.value, edges.value)
+ }
+
+ // 节点删除
+ const handleNodeDelete = (nodeId: string) => {
+ // 删除相关边
+ const relatedEdges = edges.value.filter(
+ (edge) => edge.source === nodeId || edge.target === nodeId,
+ )
+ if (relatedEdges.length > 0) {
+ removeEdges(relatedEdges.map((edge) => edge.id))
+ }
+
+ removeNodes([nodeId])
+ saveState(nodes.value, edges.value)
+ }
+
+ // 节点更新,空标签时清除自定义标签(恢复默认类型名称)
+ const handleNodeUpdate = (nodeId: string, newLabel: string) => {
+ const node = findNode(nodeId)
+ if (node) {
+ if (newLabel) {
+ node.data = { ...node.data, customLabel: newLabel }
+ } else {
+ const { customLabel: _, ...rest } = node.data
+ node.data = rest
+ }
+ saveState(nodes.value, edges.value)
+ }
+ }
+
+ // 清空画布
+ const clearCanvas = () => {
+ nodes.value = []
+ edges.value = []
+ saveState(nodes.value, edges.value)
+ }
+
+ // 删除选中的节点和边
+ const deleteSelected = () => {
+ const selectedNodes = getSelectedNodes.value
+ const selectedEdges = getSelectedEdges.value
+
+ if (selectedNodes.length > 0) {
+ removeNodes(selectedNodes.map((node) => node.id))
+ }
+ if (selectedEdges.length > 0) {
+ removeEdges(selectedEdges.map((edge) => edge.id))
+ }
+ saveState(nodes.value, edges.value)
+ }
+
+ return {
+ handleConnect,
+ handleEdgeClick,
+ handleNodeDelete,
+ handleNodeUpdate,
+ clearCanvas,
+ deleteSelected,
+ }
+}
diff --git a/apps/web/src/shared/components/FlowchartEditor/useHistory.ts b/apps/web/src/shared/components/FlowchartEditor/useHistory.ts
new file mode 100644
index 0000000..8ec9a50
--- /dev/null
+++ b/apps/web/src/shared/components/FlowchartEditor/useHistory.ts
@@ -0,0 +1,72 @@
+import { shallowRef, computed } from "vue"
+import type { Node, Edge } from "@vue-flow/core"
+
+/**
+ * 简化的历史记录管理
+ */
+export function useHistory() {
+ const history = shallowRef<{ nodes: Node[]; edges: Edge[] }[]>([])
+ const historyIndex = ref(-1)
+
+ // 是否可以撤销
+ const canUndo = computed(() => historyIndex.value > 0)
+
+ // 是否可以重做
+ const canRedo = computed(() => historyIndex.value < history.value.length - 1)
+
+ const deepCopyState = (
+ nodes: Node[],
+ edges: Edge[],
+ ): { nodes: Node[]; edges: Edge[] } =>
+ JSON.parse(JSON.stringify({ nodes, edges })) as {
+ nodes: Node[]
+ edges: Edge[]
+ }
+
+ // 保存状态到历史记录
+ const saveState = (nodes: Node[], edges: Edge[]) => {
+ const currentState = deepCopyState(nodes, edges)
+
+ // 如果当前不在历史记录的末尾,删除后面的记录
+ if (historyIndex.value < history.value.length - 1) {
+ history.value = history.value.slice(0, historyIndex.value + 1)
+ }
+
+ history.value = [...history.value, currentState]
+ historyIndex.value = history.value.length - 1
+
+ // 限制历史记录数量
+ if (history.value.length > 20) {
+ history.value = history.value.slice(1)
+ historyIndex.value--
+ }
+ }
+
+ // 撤销
+ const undo = () => {
+ if (canUndo.value) {
+ historyIndex.value--
+ const state = history.value[historyIndex.value]
+ return deepCopyState(state.nodes, state.edges)
+ }
+ return null
+ }
+
+ // 重做
+ const redo = () => {
+ if (canRedo.value) {
+ historyIndex.value++
+ const state = history.value[historyIndex.value]
+ return deepCopyState(state.nodes, state.edges)
+ }
+ return null
+ }
+
+ return {
+ canUndo,
+ canRedo,
+ saveState,
+ undo,
+ redo,
+ }
+}
diff --git a/apps/web/src/shared/components/FlowchartEditor/useNodeStyles.ts b/apps/web/src/shared/components/FlowchartEditor/useNodeStyles.ts
new file mode 100644
index 0000000..45e2114
--- /dev/null
+++ b/apps/web/src/shared/components/FlowchartEditor/useNodeStyles.ts
@@ -0,0 +1,137 @@
+/**
+ * 节点样式管理
+ */
+
+/**
+ * 获取节点类型配置
+ */
+export function getNodeTypeConfig(type: string) {
+ const configs: Record<
+ string,
+ { label: string; color: string; icon: string; description: string }
+ > = {
+ start: {
+ label: "开始",
+ color: "#10b981",
+ icon: "▶",
+ description: "流程开始",
+ },
+ input: {
+ label: "输入",
+ color: "#06b6d4",
+ icon: "📥",
+ description: "数据输入",
+ },
+ default: {
+ label: "赋值",
+ color: "#3b82f6",
+ icon: "⚙",
+ description: "赋值语句",
+ },
+ decision: {
+ label: "判断",
+ color: "#f59e0b",
+ icon: "❓",
+ description: "条件语句",
+ },
+ loop: {
+ label: "循环",
+ color: "#8b5cf6",
+ icon: "🔄",
+ description: "循环语句",
+ },
+ output: {
+ label: "输出",
+ color: "#84cc16",
+ icon: "📤",
+ description: "数据输出",
+ },
+ end: {
+ label: "结束",
+ color: "#ef4444",
+ icon: "⏹",
+ description: "流程结束",
+ },
+ }
+ return (
+ configs[type] || {
+ label: "节点",
+ color: "#6b7280",
+ icon: "⚪",
+ description: "未知节点",
+ }
+ )
+}
+
+/**
+ * 获取节点样式
+ */
+export function getNodeStyle(type: string, color: string) {
+ const baseStyle = {
+ background: color,
+ color: "white",
+ border: `2px solid ${color}`,
+ borderRadius: "10px",
+ fontSize: "16px",
+ fontWeight: "500",
+ width: "auto", // 自动宽度
+ height: "auto", // 自动高度
+ minWidth: "100px",
+ minHeight: "40px",
+ maxWidth: "400px",
+ maxHeight: "160px",
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)",
+ }
+
+ // 根据节点类型调整样式
+ switch (type) {
+ case "start":
+ case "end":
+ return {
+ ...baseStyle,
+ }
+ case "decision":
+ return {
+ ...baseStyle,
+ borderRadius: "8px",
+ minWidth: "140px",
+ minHeight: "50px",
+ }
+ case "loop":
+ return {
+ ...baseStyle,
+ borderRadius: "8px",
+ minWidth: "140px",
+ minHeight: "50px",
+ }
+ default:
+ return baseStyle
+ }
+}
+
+/**
+ * 创建节点样式
+ */
+export function createNodeStyle(type: string) {
+ const config = getNodeTypeConfig(type)
+ return getNodeStyle(type, config.color)
+}
+
+/**
+ * 获取节点尺寸
+ */
+export function getNodeDimensions(type: string) {
+ switch (type) {
+ case "start":
+ case "end":
+ return { width: 100, height: 40 }
+ case "decision":
+ case "loop":
+ return { width: 140, height: 50 }
+ default:
+ return { width: 120, height: 40 }
+ }
+}
diff --git a/apps/web/src/shared/components/FlowchartStatisticsPanel.vue b/apps/web/src/shared/components/FlowchartStatisticsPanel.vue
new file mode 100644
index 0000000..fcbdaf4
--- /dev/null
+++ b/apps/web/src/shared/components/FlowchartStatisticsPanel.vue
@@ -0,0 +1,577 @@
+
+
+
+
+
+ 统计
+
+
+
+
+
+
+
+
+ 总提交
+
+ {{ data.total_count }}
+
+
+
+ 平均分
+
+ {{ data.avg_score }}
+
+
+
+
+ 完成人数
+
+ {{ data.completed_count }}
+
+
+
+ 班级人数
+
+ {{ data.person_count }}
+
+
+
+ 完成度
+
+ {{ completionRate }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 请假隐藏中
+ 请假隐藏
+
+
+ 恢复 {{ hiddenCount }} 位
+
+
+
+
+ 全都完成了
+
+
+
+ {{ item.real_name }}
+
+ {{ item.real_name }}
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/shared/components/Header.vue b/apps/web/src/shared/components/Header.vue
new file mode 100644
index 0000000..4e4db69
--- /dev/null
+++ b/apps/web/src/shared/components/Header.vue
@@ -0,0 +1,356 @@
+
+
+
+
+
+
+
+ {{ configStore.config?.website_name }}
+ ({{ envVersion }})
+
+
+
+
+
+
+
+
+
+ 菜单
+
+
+ screenModeStore.switchScreenMode()"
+ >
+ {{ screenMode }}
+
+
+
+
+
+
+ {{ userStore.user!.username }}
+
+
+
+
+
+ 登录
+
+
+ 注册
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/shared/components/Hitokoto.vue b/apps/web/src/shared/components/Hitokoto.vue
new file mode 100644
index 0000000..f8ae727
--- /dev/null
+++ b/apps/web/src/shared/components/Hitokoto.vue
@@ -0,0 +1,69 @@
+
+
+
+ {{ "来自 " + hitokoto.from }}
+ {{ hitokoto.sentence }}
+
+
+
diff --git a/apps/web/src/shared/components/IconButton.vue b/apps/web/src/shared/components/IconButton.vue
new file mode 100644
index 0000000..8fbe0b1
--- /dev/null
+++ b/apps/web/src/shared/components/IconButton.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+ {{ tip }}
+
+
+
diff --git a/apps/web/src/shared/components/Login.vue b/apps/web/src/shared/components/Login.vue
new file mode 100644
index 0000000..25e565d
--- /dev/null
+++ b/apps/web/src/shared/components/Login.vue
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+ 关于【选择班级】的提醒:
+ 1. 如果是上课统一生成的账号,选择【相应班级】,用户名直接写自己的名字
+
+ 2.
+ 同样是上课用的号,但是没有你的班级。选择【没有我所在的班级】,用户名要写:ks班级+姓名,比如23计算机1班张三,就写ks231张三
+
+ 3. 如果是自己注册的号,选择【没有我所在的班级】
+
+
+
+
+
+
+
+
+
+
+
+ {{ msg }}
+
+
+
+ 登录
+
+
+ 没有账号?立即注册
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/shared/components/LoginSummaryModal.vue b/apps/web/src/shared/components/LoginSummaryModal.vue
new file mode 100644
index 0000000..be3083b
--- /dev/null
+++ b/apps/web/src/shared/components/LoginSummaryModal.vue
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+ 上次登录时间:{{ lastLoginTime }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AI 分析
+
+ {{ loginSummaryStore.analysisError }}
+
+
+
+
+
+
+
diff --git a/apps/web/src/shared/components/MarkdownEditor.vue b/apps/web/src/shared/components/MarkdownEditor.vue
new file mode 100644
index 0000000..c0ee0f7
--- /dev/null
+++ b/apps/web/src/shared/components/MarkdownEditor.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
diff --git a/apps/web/src/shared/components/MermaidEditor.vue b/apps/web/src/shared/components/MermaidEditor.vue
new file mode 100644
index 0000000..238e5ac
--- /dev/null
+++ b/apps/web/src/shared/components/MermaidEditor.vue
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+ Mermaid 代码
+
+
+ 复制
+
+
+ 清空
+
+
+
+
+
+
+
+ 图表预览
+
+ ✓ 渲染成功
+
+
+
+ {{ renderError }}
+
+
+
+
+
+
+
diff --git a/apps/web/src/shared/components/Pagination.vue b/apps/web/src/shared/components/Pagination.vue
new file mode 100644
index 0000000..478d30d
--- /dev/null
+++ b/apps/web/src/shared/components/Pagination.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+
diff --git a/apps/web/src/shared/components/Signup.vue b/apps/web/src/shared/components/Signup.vue
new file mode 100644
index 0000000..1e22389
--- /dev/null
+++ b/apps/web/src/shared/components/Signup.vue
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ msg }}
+
+
+
+ 注册
+
+ 已经注册?现在登录
+
+
+
+
+
diff --git a/apps/web/src/shared/components/StatisticsPanel.vue b/apps/web/src/shared/components/StatisticsPanel.vue
new file mode 100644
index 0000000..52bd0b4
--- /dev/null
+++ b/apps/web/src/shared/components/StatisticsPanel.vue
@@ -0,0 +1,470 @@
+
+
+
+
+
+ 统计
+
+ 前往提交列表
+
+
+
+
+
+
+
+
+
+ 总提交
+ {{
+ count.total
+ }}
+
+
+ 正确提交
+ {{
+ count.accepted
+ }}
+
+
+ 正确率
+ {{
+ count.rate
+ }}
+
+
+
+ 完成人数
+ {{
+ list.length
+ }}
+
+
+ 班级人数
+ {{
+ adjustedPersonCount
+ }}
+
+
+ 完成度
+ {{
+ adjustedPersonRate
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 请假隐藏中
+ 请假隐藏
+
+
+ 恢复 {{ hiddenCount }} 位
+
+
+
+
+ 全都完成了
+
+
+
+ {{ item.real_name }}
+
+ {{ item.real_name }}
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/shared/components/SubmissionResultTag.vue b/apps/web/src/shared/components/SubmissionResultTag.vue
new file mode 100644
index 0000000..9bf859f
--- /dev/null
+++ b/apps/web/src/shared/components/SubmissionResultTag.vue
@@ -0,0 +1,17 @@
+
+
+
+
+ {{ JUDGE_STATUS[result]["name"] }}
+
+
+
+
diff --git a/apps/web/src/shared/components/SyncCodeEditor.vue b/apps/web/src/shared/components/SyncCodeEditor.vue
new file mode 100644
index 0000000..9c18670
--- /dev/null
+++ b/apps/web/src/shared/components/SyncCodeEditor.vue
@@ -0,0 +1,153 @@
+
+
+
+
+
diff --git a/apps/web/src/shared/components/TextCopy.vue b/apps/web/src/shared/components/TextCopy.vue
new file mode 100644
index 0000000..551c561
--- /dev/null
+++ b/apps/web/src/shared/components/TextCopy.vue
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+ 点击复制
+
+
+
diff --git a/apps/web/src/shared/components/TextEditor.vue b/apps/web/src/shared/components/TextEditor.vue
new file mode 100644
index 0000000..2bc8097
--- /dev/null
+++ b/apps/web/src/shared/components/TextEditor.vue
@@ -0,0 +1,144 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
diff --git a/apps/web/src/shared/components/UserBadge.vue b/apps/web/src/shared/components/UserBadge.vue
new file mode 100644
index 0000000..9688acb
--- /dev/null
+++ b/apps/web/src/shared/components/UserBadge.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ {{ badge.badge.name }}
+ 获得条件:{{ getConditionText() }}
+
+ 获得时间:{{ parseTime(badge.earned_time, "YYYY-MM-DD HH:mm:ss") }}
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/shared/composables/breakpoints.ts b/apps/web/src/shared/composables/breakpoints.ts
new file mode 100644
index 0000000..71cc669
--- /dev/null
+++ b/apps/web/src/shared/composables/breakpoints.ts
@@ -0,0 +1,20 @@
+import {
+ breakpointsTailwind,
+ useBreakpoints as useVueUseBreakpoints,
+} from "@vueuse/core"
+
+/**
+ * 响应式断点检测 composable
+ * 每次调用创建新的断点检测实例
+ */
+export function useBreakpoints() {
+ const breakpoints = useVueUseBreakpoints(breakpointsTailwind)
+
+ const isMobile = breakpoints.smallerOrEqual("md")
+ const isDesktop = breakpoints.greater("md")
+
+ return {
+ isMobile,
+ isDesktop,
+ }
+}
diff --git a/apps/web/src/shared/composables/configUpdate.ts b/apps/web/src/shared/composables/configUpdate.ts
new file mode 100644
index 0000000..4974f36
--- /dev/null
+++ b/apps/web/src/shared/composables/configUpdate.ts
@@ -0,0 +1,41 @@
+import { useConfigStore } from "shared/store/config"
+import { useUserStore } from "shared/store/user"
+import {
+ useConfigWebSocket,
+ type ConfigUpdate,
+} from "shared/composables/websocket"
+
+export function useConfigUpdate() {
+ const configStore = useConfigStore()
+ const userStore = useUserStore()
+
+ // 处理 WebSocket 配置更新
+ const handleConfigUpdate = (data: ConfigUpdate) => {
+ // 更新全局配置 - 使用响应式方式
+ if (data.key in configStore.config) {
+ // 直接修改 ref 的值来触发响应式更新
+ ;(configStore.config as any)[data.key] = data.value
+ }
+ }
+
+ // 初始化 WebSocket - handler 会在 onMounted 时自动添加
+ const { connect, disconnect } = useConfigWebSocket(handleConfigUpdate)
+
+ // 监听登录状态变化
+ watch(
+ () => userStore.isAuthed,
+ (isAuthed) => {
+ if (isAuthed) {
+ connect()
+ } else {
+ disconnect()
+ }
+ },
+ { immediate: true },
+ )
+
+ return {
+ connect,
+ disconnect,
+ }
+}
diff --git a/apps/web/src/shared/composables/learnProgress.ts b/apps/web/src/shared/composables/learnProgress.ts
new file mode 100644
index 0000000..8a638e6
--- /dev/null
+++ b/apps/web/src/shared/composables/learnProgress.ts
@@ -0,0 +1,15 @@
+import { useStorage } from "@vueuse/core"
+import { STORAGE_KEY } from "utils/constants"
+
+export type TutorialType = "python" | "c"
+
+// 模块级单例:学习页写入、导航栏读取,必须共用同一个响应式引用,
+// 否则导航栏的链接不会随学习进度更新
+const learnStep = useStorage>(
+ STORAGE_KEY.LEARN_CURRENT_STEP,
+ { python: 1, c: 1 },
+)
+
+export function useLearnProgress() {
+ return { learnStep }
+}
diff --git a/apps/web/src/shared/composables/maxkb.ts b/apps/web/src/shared/composables/maxkb.ts
new file mode 100644
index 0000000..a84f909
--- /dev/null
+++ b/apps/web/src/shared/composables/maxkb.ts
@@ -0,0 +1,132 @@
+import { useConfigStore } from "shared/store/config"
+import { useUserStore } from "shared/store/user"
+import {
+ useConfigWebSocket,
+ type ConfigUpdate,
+} from "shared/composables/websocket"
+
+export function useMaxKB() {
+ const configStore = useConfigStore()
+ const userStore = useUserStore()
+ const isLoaded = ref(false)
+
+ // 处理 WebSocket 配置更新 - 只处理 MaxKB 相关
+ const handleConfigUpdate = (data: ConfigUpdate) => {
+ if (data.key === "enable_maxkb") {
+ if (data.value) {
+ loadMaxKBScript()
+ } else {
+ removeMaxKBScript()
+ }
+ }
+ }
+
+ // 初始化 WebSocket
+ const { connect, disconnect } = useConfigWebSocket(handleConfigUpdate)
+
+ // 监听登录状态变化
+ watch(
+ () => userStore.isAuthed,
+ (isAuthed) => {
+ if (isAuthed) {
+ connect()
+ } else {
+ disconnect()
+ }
+ },
+ { immediate: true },
+ )
+
+ const loadMaxKBScript = () => {
+ const { enable_maxkb } = configStore.config
+
+ if (!enable_maxkb) {
+ return
+ }
+
+ const existingScript = document.querySelector(
+ `script[src="${import.meta.env.PUBLIC_MAXKB_URL}"]`,
+ )
+ if (existingScript) {
+ isLoaded.value = true
+ return
+ }
+
+ // 创建并插入脚本标签
+ const script = document.createElement("script")
+ script.src = import.meta.env.PUBLIC_MAXKB_URL
+ script.async = true
+ script.defer = true
+
+ script.onload = () => {
+ isLoaded.value = true
+ }
+
+ script.onerror = () => {
+ console.error("Failed to load MaxKB script")
+ }
+
+ document.head.appendChild(script)
+ }
+
+ const removeMaxKBScript = () => {
+ // 把 script 也删除
+ const script = document.querySelector(
+ `script[src="${import.meta.env.PUBLIC_MAXKB_URL}"]`,
+ )
+ if (script) {
+ script.remove()
+ }
+ // 等待DOM加载完成后删除所有id以"maxkb-"开头的元素
+ const removeMaxKBElements = () => {
+ // 查找所有id以"maxkb-"开头的元素
+ const elements = document.querySelectorAll('[id^="maxkb-"]')
+
+ elements.forEach((element) => {
+ element.remove()
+ })
+ }
+
+ // 如果DOM已经加载完成,直接执行删除
+ if (document.readyState === "complete") {
+ removeMaxKBElements()
+ } else {
+ // 等待DOM加载完成
+ window.addEventListener("load", removeMaxKBElements, { once: true })
+ }
+
+ // 移除MaxKB脚本标签
+ const existingScript = document.querySelector(
+ `script[src="${import.meta.env.PUBLIC_MAXKB_URL}"]`,
+ )
+ if (existingScript) {
+ existingScript.remove()
+ }
+
+ // 重置加载状态
+ isLoaded.value = false
+ }
+
+ // 连接 WebSocket
+ onMounted(() => {
+ connect()
+ })
+
+ watch(
+ () => configStore.config.enable_maxkb,
+ (enabled) => {
+ if (enabled) {
+ loadMaxKBScript()
+ } else {
+ removeMaxKBScript()
+ }
+ },
+ { immediate: true },
+ )
+
+ return {
+ loadMaxKBScript,
+ removeMaxKBScript,
+ isLoaded: readonly(isLoaded),
+ }
+}
diff --git a/apps/web/src/shared/composables/pagination.ts b/apps/web/src/shared/composables/pagination.ts
new file mode 100644
index 0000000..f0204e9
--- /dev/null
+++ b/apps/web/src/shared/composables/pagination.ts
@@ -0,0 +1,154 @@
+import { reactive, watch } from "vue"
+import { useRoute, useRouter } from "vue-router"
+import { filterEmptyValue } from "utils/functions"
+
+export interface PaginationQuery {
+ page: number
+ limit: number
+ [key: string]: any
+}
+
+export interface UsePaginationOptions {
+ /** 默认每页条数 */
+ defaultLimit?: number
+ /** 默认页码 */
+ defaultPage?: number
+ /** 当其他查询条件变化时是否重置页码 */
+ resetPageOnChange?: boolean
+}
+
+/**
+ * 分页相关的 composable,处理分页状态和 URL 同步
+ * 每次调用创建新的分页状态实例
+ * @param initialQuery 初始查询参数对象
+ * @param options 配置选项
+ */
+export function usePagination>(
+ initialQuery: Omit = {} as Omit,
+ options: UsePaginationOptions = {},
+) {
+ const {
+ defaultLimit = 10,
+ defaultPage = 1,
+ resetPageOnChange = true,
+ } = options
+
+ const route = useRoute()
+ const router = useRouter()
+
+ // 从 URL 查询参数初始化状态
+ const query = reactive({
+ page: parseInt(route.query.page) || defaultPage,
+ limit: parseInt(route.query.limit) || defaultLimit,
+ ...initialQuery,
+ }) as unknown as T & PaginationQuery
+
+ // 同步 URL 查询参数到本地状态
+ function syncFromRoute() {
+ ;(query as any).page = parseInt(route.query.page) || defaultPage
+ ;(query as any).limit = parseInt(route.query.limit) || defaultLimit
+
+ // 同步其他查询参数
+ Object.keys(initialQuery).forEach((key) => {
+ const value = route.query[key]
+ if (value !== undefined) {
+ // 处理不同类型的参数
+ if (typeof initialQuery[key] === "boolean") {
+ ;(query as any)[key] = value === "1" || value === "true"
+ } else if (typeof initialQuery[key] === "number") {
+ ;(query as any)[key] = parseInt(value) || initialQuery[key]
+ } else {
+ ;(query as any)[key] = value || initialQuery[key]
+ }
+ }
+ })
+ }
+
+ // 更新 URL
+ function updateRoute() {
+ const newQuery = filterEmptyValue(query)
+ router.push({
+ path: route.path,
+ query: newQuery,
+ })
+ }
+
+ // 重置页码到第一页
+ function resetPage() {
+ ;(query as any).page = defaultPage
+ }
+
+ // 清空所有查询条件(除了分页参数)
+ function clearQuery() {
+ Object.keys(initialQuery).forEach((key) => {
+ const initialValue = initialQuery[key]
+ if (typeof initialValue === "string") {
+ ;(query as any)[key] = ""
+ } else if (typeof initialValue === "boolean") {
+ ;(query as any)[key] = false
+ } else if (typeof initialValue === "number") {
+ ;(query as any)[key] = 0
+ } else {
+ ;(query as any)[key] = initialValue
+ }
+ })
+ resetPage()
+ }
+
+ // 监听页码变化,同步到 URL
+ watch(() => query.page, updateRoute)
+
+ // 监听每页条数变化,重置页码并同步到 URL
+ watch(
+ () => query.limit,
+ () => {
+ if (resetPageOnChange) {
+ resetPage()
+ }
+ updateRoute()
+ },
+ )
+
+ // 监听其他查询条件变化,重置页码并同步到 URL
+ if (resetPageOnChange && Object.keys(initialQuery).length > 0) {
+ const otherQueryKeys = Object.keys(initialQuery)
+ watch(
+ () => otherQueryKeys.map((key) => query[key]),
+ () => {
+ resetPage()
+ updateRoute()
+ },
+ { deep: true },
+ )
+ }
+
+ // 监听路由变化,同步到本地状态
+ watch(
+ () => route.query,
+ () => {
+ syncFromRoute()
+ },
+ { deep: true },
+ )
+
+ return {
+ query,
+ updateRoute,
+ resetPage,
+ clearQuery,
+ syncFromRoute,
+ }
+}
+
+/**
+ * 简化版本的分页 composable,只处理基本的分页逻辑
+ * 每次调用创建新的分页状态实例
+ * @param defaultLimit 默认每页条数
+ * @param defaultPage 默认页码
+ */
+export function useSimplePagination(defaultLimit = 10, defaultPage = 1) {
+ return usePagination(
+ {},
+ { defaultLimit, defaultPage, resetPageOnChange: false },
+ )
+}
diff --git a/apps/web/src/shared/composables/rarity.ts b/apps/web/src/shared/composables/rarity.ts
new file mode 100644
index 0000000..237170c
--- /dev/null
+++ b/apps/web/src/shared/composables/rarity.ts
@@ -0,0 +1,15 @@
+import { useDark } from "@vueuse/core"
+import { computed } from "vue"
+import { RARITY_TEXT_COLOR } from "utils/constants"
+
+/**
+ * 成就稀有度的文字配色,跟随明暗主题切换。
+ * 两套色值都压到 4.5:1 以上,不然浅色模式下白金和黄金的小字看不清。
+ * 边框和色块不用这个,直接用 RARITY_COLOR 的原色。
+ */
+export function useRarityColor() {
+ const isDark = useDark()
+ return computed(() =>
+ isDark.value ? RARITY_TEXT_COLOR.dark : RARITY_TEXT_COLOR.light,
+ )
+}
diff --git a/apps/web/src/shared/composables/sync.ts b/apps/web/src/shared/composables/sync.ts
new file mode 100644
index 0000000..7dfa0f0
--- /dev/null
+++ b/apps/web/src/shared/composables/sync.ts
@@ -0,0 +1,412 @@
+import { useMessage } from "naive-ui"
+import { useUserStore } from "../store/user"
+import type { EditorView } from "@codemirror/view"
+import { Compartment } from "@codemirror/state"
+import type { WebrtcProvider } from "y-webrtc"
+import type { Doc, Text } from "yjs"
+
+// 常量定义
+const SYNC_CONSTANTS = {
+ MAX_ROOM_USERS: 2,
+ AWARENESS_SYNC_DELAY: 500,
+ INIT_SYNC_TIMEOUT: 500,
+ SUPER_ADMIN_COLOR: "#ff6b6b",
+ REGULAR_USER_COLOR: "#4dabf7",
+} as const
+
+// 错误类型码
+export const SYNC_ERROR_CODES = {
+ SUPER_ADMIN_LEFT: "SUPER_ADMIN_LEFT",
+ MISSING_SUPER_ADMIN: "MISSING_SUPER_ADMIN",
+} as const
+
+// 界面和通知文案
+export const SYNC_MESSAGES = {
+ // 超管离开
+ SUPER_ADMIN_LEFT: (name: string) => `超管 ${name} 已离开`,
+
+ // 缺少超管
+ MISSING_SUPER_ADMIN: "协同编辑需要超管",
+
+ // 连接成功
+ SYNC_ACTIVE: "协同编辑已激活!",
+
+ // 连接断开
+ CONNECTION_LOST: "协同编辑已断开",
+
+ // 等待相关
+ WAITING_STUDENT: "正在等待学生加入...",
+ WAITING_ADMIN: "正在等待超管加入...",
+
+ // Form.vue 界面文案
+ SYNC_ON: "断开同步",
+ SYNC_OFF: "开启同步",
+ SYNCING_WITH: (name: string) => `与 ${name} 同步中`,
+ STUDENT_LEFT: (name?: string) => (name ? `${name}已离开` : "可以关闭同步"),
+} as const
+
+// 类型定义
+type SyncState = "waiting" | "active" | "error"
+type SyncErrorCode = (typeof SYNC_ERROR_CODES)[keyof typeof SYNC_ERROR_CODES]
+
+interface UserInfo {
+ name: string
+ isSuperAdmin: boolean
+}
+
+interface PeersEvent {
+ webrtcPeers: string[]
+}
+
+interface StatusEvent {
+ connected: boolean
+}
+
+interface SyncedEvent {
+ synced: boolean
+}
+
+interface SyncOptions {
+ problemId: string
+ editorView: EditorView
+ onStatusChange?: (status: SyncStatus) => void
+}
+
+export interface SyncStatus {
+ connected: boolean
+ roomUsers: number
+ canSync: boolean
+ message: string
+ error?: string
+ errorCode?: SyncErrorCode
+ otherUser?: UserInfo
+}
+
+/**
+ * 代码同步 composable
+ * 每次调用创建新的同步实例
+ */
+export function useCodeSync() {
+ const userStore = useUserStore()
+ const message = useMessage()
+
+ // 每次调用创建新的实例变量
+ let ydoc: Doc | null = null
+ let provider: WebrtcProvider | null = null
+ let ytext: Text | null = null
+ const collabCompartment = new Compartment()
+ let currentEditorView: EditorView | null = null
+ let lastSyncState: SyncState | null = null
+ let roomUserInfo = new Map()
+ let hasShownSuperAdminLeftMessage = false
+
+ const updateStatus = (
+ status: SyncStatus,
+ onStatusChange?: (status: SyncStatus) => void,
+ ) => {
+ onStatusChange?.(status)
+ }
+
+ const normalizeClientId = (clientId: number | string): number => {
+ return typeof clientId === "string" ? parseInt(clientId, 10) : clientId
+ }
+
+ const checkHasSuperAdmin = (awarenessStates: Map): boolean => {
+ if (userStore.isSuperAdmin) return true
+ return Array.from(awarenessStates.values()).some(
+ (state) => state.user?.isSuperAdmin,
+ )
+ }
+
+ const getOtherUserInfo = (
+ awarenessStates: Map,
+ ): UserInfo | undefined => {
+ if (!provider) return undefined
+
+ const localClientId = provider.awareness.clientID
+ for (const [clientId, state] of awarenessStates) {
+ if (clientId !== localClientId && state.user) {
+ return {
+ name: state.user.name,
+ isSuperAdmin: state.user.isSuperAdmin,
+ }
+ }
+ }
+ return undefined
+ }
+
+ const checkIfSuperAdminLeft = (
+ removedClientIds: number[],
+ onStatusChange?: (status: SyncStatus) => void,
+ ) => {
+ if (userStore.isSuperAdmin || hasShownSuperAdminLeftMessage) return
+
+ const superAdminInfo = removedClientIds
+ .map((id) => roomUserInfo.get(id))
+ .find((info) => info?.isSuperAdmin)
+
+ if (superAdminInfo) {
+ hasShownSuperAdminLeftMessage = true
+ const leftMessage = SYNC_MESSAGES.SUPER_ADMIN_LEFT(superAdminInfo.name)
+ updateStatus(
+ {
+ connected: false,
+ roomUsers: 0,
+ canSync: false,
+ message: leftMessage,
+ error: leftMessage,
+ errorCode: SYNC_ERROR_CODES.SUPER_ADMIN_LEFT,
+ },
+ onStatusChange,
+ )
+ message.warning(leftMessage)
+ stopSync()
+ }
+ }
+
+ const checkRoomPermissions = (
+ roomUsers: number,
+ onStatusChange?: (status: SyncStatus) => void,
+ ) => {
+ const awarenessStates = provider?.awareness.getStates()
+ if (!awarenessStates) return
+
+ const hasSuperAdmin = checkHasSuperAdmin(awarenessStates)
+ const canSync = roomUsers === SYNC_CONSTANTS.MAX_ROOM_USERS && hasSuperAdmin
+ const otherUser = getOtherUserInfo(awarenessStates)
+
+ if (roomUsers === SYNC_CONSTANTS.MAX_ROOM_USERS && !hasSuperAdmin) {
+ if (lastSyncState === "error") return
+
+ updateStatus(
+ {
+ connected: false,
+ roomUsers,
+ canSync: false,
+ message: SYNC_MESSAGES.MISSING_SUPER_ADMIN,
+ error: SYNC_MESSAGES.MISSING_SUPER_ADMIN,
+ errorCode: SYNC_ERROR_CODES.MISSING_SUPER_ADMIN,
+ otherUser,
+ },
+ onStatusChange,
+ )
+ message.error(SYNC_MESSAGES.MISSING_SUPER_ADMIN)
+ lastSyncState = "error"
+ stopSync()
+ return
+ } else if (canSync) {
+ updateStatus(
+ {
+ connected: true,
+ roomUsers,
+ canSync: true,
+ message: SYNC_MESSAGES.SYNC_ACTIVE,
+ otherUser,
+ },
+ onStatusChange,
+ )
+ if (lastSyncState !== "active") {
+ message.success(SYNC_MESSAGES.SYNC_ACTIVE)
+ lastSyncState = "active"
+ }
+ } else {
+ updateStatus(
+ {
+ connected: true,
+ roomUsers,
+ canSync: false,
+ message:
+ roomUsers === 1
+ ? SYNC_MESSAGES.WAITING_STUDENT
+ : SYNC_MESSAGES.WAITING_ADMIN,
+ otherUser,
+ },
+ onStatusChange,
+ )
+ lastSyncState = "waiting"
+ }
+ }
+
+ const setupContentSync = (
+ ytext: Text,
+ provider: WebrtcProvider,
+ savedContent: string,
+ ) => {
+ let hasInitialized = false
+
+ const initTimeout = setTimeout(() => {
+ if (!hasInitialized && ytext.length === 0 && savedContent) {
+ ytext.insert(0, savedContent)
+ }
+ hasInitialized = true
+ }, SYNC_CONSTANTS.INIT_SYNC_TIMEOUT)
+
+ provider.on("synced", (event: SyncedEvent) => {
+ if (!event.synced || hasInitialized) return
+
+ clearTimeout(initTimeout)
+ if (ytext.length === 0 && savedContent) {
+ ytext.insert(0, savedContent)
+ }
+ hasInitialized = true
+ })
+ }
+
+ async function startSync(options: SyncOptions): Promise<() => void> {
+ const { problemId, editorView, onStatusChange } = options
+
+ if (!userStore.isAuthed) {
+ return () => {}
+ }
+
+ // 动态导入 yjs 相关模块
+ const [Y, { WebrtcProvider }, { yCollab }] = await Promise.all([
+ import("yjs"),
+ import("y-webrtc"),
+ import("y-codemirror.next"),
+ ])
+
+ // 初始化文档和提供者
+ ydoc = new Y.Doc()
+ ytext = ydoc.getText("codemirror")
+ const roomName = `problem-${problemId}`
+
+ provider = new WebrtcProvider(roomName, ydoc, {
+ signaling: [import.meta.env.PUBLIC_SIGNALING_URL],
+ maxConns: 1,
+ filterBcConns: true,
+ })
+
+ // 监听连接状态
+ provider.on("status", (event: StatusEvent) => {
+ if (!event.connected) {
+ updateStatus(
+ {
+ connected: false,
+ roomUsers: 0,
+ canSync: false,
+ message: SYNC_MESSAGES.CONNECTION_LOST,
+ error: SYNC_MESSAGES.CONNECTION_LOST,
+ },
+ onStatusChange,
+ )
+ message.warning(SYNC_MESSAGES.CONNECTION_LOST)
+ }
+ })
+
+ // 监听用户加入/离开
+ provider.on("peers", (event: PeersEvent) => {
+ const roomUsers = event.webrtcPeers.length + 1
+ setTimeout(() => {
+ checkRoomPermissions(roomUsers, onStatusChange)
+ }, SYNC_CONSTANTS.AWARENESS_SYNC_DELAY)
+ })
+
+ // 监听 awareness 变化
+ provider.awareness.on("change", (changes: any) => {
+ if (!provider) return
+
+ const awarenessStates = provider.awareness.getStates()
+
+ if (changes.removed?.length > 0) {
+ checkIfSuperAdminLeft(changes.removed, onStatusChange)
+ }
+
+ awarenessStates.forEach((state, clientId) => {
+ if (state.user) {
+ const normalizedId = normalizeClientId(clientId)
+ roomUserInfo.set(normalizedId, {
+ name: state.user.name,
+ isSuperAdmin: state.user.isSuperAdmin,
+ })
+ }
+ })
+
+ checkRoomPermissions(awarenessStates.size, onStatusChange)
+ })
+
+ // 配置编辑器扩展
+ if (editorView && ytext) {
+ currentEditorView = editorView
+ const userColor = userStore.isSuperAdmin
+ ? SYNC_CONSTANTS.SUPER_ADMIN_COLOR
+ : SYNC_CONSTANTS.REGULAR_USER_COLOR
+ const userName = userStore.user?.username || "匿名用户"
+ const savedContent = editorView.state.doc.toString()
+
+ // 设置用户信息
+ provider.awareness.setLocalStateField("user", {
+ name: userName,
+ color: userColor,
+ isSuperAdmin: userStore.isSuperAdmin,
+ })
+
+ // 清空编辑器并应用协同扩展
+ editorView.dispatch({
+ changes: { from: 0, to: editorView.state.doc.length, insert: "" },
+ })
+
+ const collabExt = yCollab(ytext, provider.awareness)
+ editorView.dispatch({
+ effects: collabCompartment.reconfigure(collabExt),
+ })
+
+ // 设置内容同步
+ setupContentSync(ytext, provider, savedContent)
+
+ // 设置初始状态
+ const waitingMessage = userStore.isSuperAdmin
+ ? SYNC_MESSAGES.WAITING_STUDENT
+ : SYNC_MESSAGES.WAITING_ADMIN
+
+ updateStatus(
+ {
+ connected: true,
+ roomUsers: 1,
+ canSync: false,
+ message: waitingMessage,
+ },
+ onStatusChange,
+ )
+
+ message.info(waitingMessage)
+ lastSyncState = "waiting"
+ }
+
+ return () => stopSync()
+ }
+
+ function stopSync() {
+ if (currentEditorView) {
+ try {
+ currentEditorView.dispatch({
+ effects: collabCompartment.reconfigure([]),
+ })
+ } catch (error) {
+ console.warn("移除协同编辑扩展失败:", error)
+ }
+ currentEditorView = null
+ }
+
+ provider?.disconnect()
+ provider?.destroy()
+ ydoc?.destroy()
+
+ provider = null
+ ydoc = null
+ ytext = null
+ lastSyncState = null
+ roomUserInfo.clear()
+ hasShownSuperAdminLeftMessage = false
+ }
+
+ function getInitialExtension() {
+ return collabCompartment.of([])
+ }
+
+ return {
+ startSync,
+ stopSync,
+ getInitialExtension,
+ }
+}
diff --git a/apps/web/src/shared/composables/useMermaid.ts b/apps/web/src/shared/composables/useMermaid.ts
new file mode 100644
index 0000000..0925627
--- /dev/null
+++ b/apps/web/src/shared/composables/useMermaid.ts
@@ -0,0 +1,334 @@
+import { getRandomId } from "utils/functions"
+
+const mermaidThemeVariables = {
+ primaryColor: "#e0f2fe",
+ primaryTextColor: "#0f172a",
+ primaryBorderColor: "#0284c7",
+ lineColor: "#64748b",
+ arrowheadColor: "#64748b",
+ secondaryColor: "#f5f3ff",
+ tertiaryColor: "#ecfdf5",
+ background: "#ffffff",
+ mainBkg: "#f8fafc",
+ secondBkg: "#eef2ff",
+ tertiaryBkg: "#f0fdfa",
+ nodeBorder: "#2563eb",
+ clusterBkg: "#f8fafc",
+ clusterBorder: "#cbd5e1",
+ edgeLabelBackground: "#ffffff",
+ fontFamily:
+ 'Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
+}
+
+const semanticNodeClasses = [
+ "startNode",
+ "endNode",
+ "startEnd",
+ "input",
+ "output",
+ "process",
+ "decision",
+ "loop",
+]
+
+const displayStyleId = "oj-mermaid-display-style"
+
+const mermaidDisplayStyle = `
+.oj-mermaid-flowchart {
+ max-width: 100%;
+ height: auto;
+}
+
+.oj-mermaid-flowchart g.node rect,
+.oj-mermaid-flowchart g.node polygon,
+.oj-mermaid-flowchart g.node ellipse,
+.oj-mermaid-flowchart g.node circle,
+.oj-mermaid-flowchart g.node path {
+ stroke-width: 2px !important;
+ filter: drop-shadow(0 6px 12px rgba(15, 23, 42, 0.12));
+}
+
+.oj-mermaid-flowchart g.node.startNode rect,
+.oj-mermaid-flowchart g.node.startNode polygon,
+.oj-mermaid-flowchart g.node.startNode ellipse,
+.oj-mermaid-flowchart g.node.startNode circle,
+.oj-mermaid-flowchart g.node.startNode path,
+.oj-mermaid-flowchart g.node.startEnd rect,
+.oj-mermaid-flowchart g.node.startEnd polygon,
+.oj-mermaid-flowchart g.node.startEnd ellipse,
+.oj-mermaid-flowchart g.node.startEnd circle,
+.oj-mermaid-flowchart g.node.startEnd path {
+ fill: #dcfce7 !important;
+ stroke: #16a34a !important;
+}
+
+.oj-mermaid-flowchart g.node.endNode rect,
+.oj-mermaid-flowchart g.node.endNode polygon,
+.oj-mermaid-flowchart g.node.endNode ellipse,
+.oj-mermaid-flowchart g.node.endNode circle,
+.oj-mermaid-flowchart g.node.endNode path {
+ fill: #fee2e2 !important;
+ stroke: #dc2626 !important;
+}
+
+.oj-mermaid-flowchart g.node.input rect,
+.oj-mermaid-flowchart g.node.input polygon,
+.oj-mermaid-flowchart g.node.input ellipse,
+.oj-mermaid-flowchart g.node.input circle,
+.oj-mermaid-flowchart g.node.input path {
+ fill: #dbeafe !important;
+ stroke: #2563eb !important;
+}
+
+.oj-mermaid-flowchart g.node.output rect,
+.oj-mermaid-flowchart g.node.output polygon,
+.oj-mermaid-flowchart g.node.output ellipse,
+.oj-mermaid-flowchart g.node.output circle,
+.oj-mermaid-flowchart g.node.output path {
+ fill: #ede9fe !important;
+ stroke: #7c3aed !important;
+}
+
+.oj-mermaid-flowchart g.node.process rect,
+.oj-mermaid-flowchart g.node.process polygon,
+.oj-mermaid-flowchart g.node.process ellipse,
+.oj-mermaid-flowchart g.node.process circle,
+.oj-mermaid-flowchart g.node.process path {
+ fill: #f0f9ff !important;
+ stroke: #0284c7 !important;
+}
+
+.oj-mermaid-flowchart g.node.decision rect,
+.oj-mermaid-flowchart g.node.decision polygon,
+.oj-mermaid-flowchart g.node.decision ellipse,
+.oj-mermaid-flowchart g.node.decision circle,
+.oj-mermaid-flowchart g.node.decision path {
+ fill: #fef3c7 !important;
+ stroke: #d97706 !important;
+}
+
+.oj-mermaid-flowchart g.node.loop rect,
+.oj-mermaid-flowchart g.node.loop polygon,
+.oj-mermaid-flowchart g.node.loop ellipse,
+.oj-mermaid-flowchart g.node.loop circle,
+.oj-mermaid-flowchart g.node.loop path {
+ fill: #fae8ff !important;
+ stroke: #c026d3 !important;
+}
+
+.oj-mermaid-flowchart g.node.oj-node-palette-0 rect,
+.oj-mermaid-flowchart g.node.oj-node-palette-0 polygon,
+.oj-mermaid-flowchart g.node.oj-node-palette-0 ellipse,
+.oj-mermaid-flowchart g.node.oj-node-palette-0 circle,
+.oj-mermaid-flowchart g.node.oj-node-palette-0 path {
+ fill: #dbeafe !important;
+ stroke: #2563eb !important;
+}
+
+.oj-mermaid-flowchart g.node.oj-node-palette-1 rect,
+.oj-mermaid-flowchart g.node.oj-node-palette-1 polygon,
+.oj-mermaid-flowchart g.node.oj-node-palette-1 ellipse,
+.oj-mermaid-flowchart g.node.oj-node-palette-1 circle,
+.oj-mermaid-flowchart g.node.oj-node-palette-1 path {
+ fill: #ccfbf1 !important;
+ stroke: #0d9488 !important;
+}
+
+.oj-mermaid-flowchart g.node.oj-node-palette-2 rect,
+.oj-mermaid-flowchart g.node.oj-node-palette-2 polygon,
+.oj-mermaid-flowchart g.node.oj-node-palette-2 ellipse,
+.oj-mermaid-flowchart g.node.oj-node-palette-2 circle,
+.oj-mermaid-flowchart g.node.oj-node-palette-2 path {
+ fill: #ede9fe !important;
+ stroke: #7c3aed !important;
+}
+
+.oj-mermaid-flowchart g.node.oj-node-palette-3 rect,
+.oj-mermaid-flowchart g.node.oj-node-palette-3 polygon,
+.oj-mermaid-flowchart g.node.oj-node-palette-3 ellipse,
+.oj-mermaid-flowchart g.node.oj-node-palette-3 circle,
+.oj-mermaid-flowchart g.node.oj-node-palette-3 path {
+ fill: #ffe4e6 !important;
+ stroke: #e11d48 !important;
+}
+
+.oj-mermaid-flowchart g.node.oj-node-palette-4 rect,
+.oj-mermaid-flowchart g.node.oj-node-palette-4 polygon,
+.oj-mermaid-flowchart g.node.oj-node-palette-4 ellipse,
+.oj-mermaid-flowchart g.node.oj-node-palette-4 circle,
+.oj-mermaid-flowchart g.node.oj-node-palette-4 path {
+ fill: #fef3c7 !important;
+ stroke: #d97706 !important;
+}
+
+.oj-mermaid-flowchart g.node.oj-node-palette-5 rect,
+.oj-mermaid-flowchart g.node.oj-node-palette-5 polygon,
+.oj-mermaid-flowchart g.node.oj-node-palette-5 ellipse,
+.oj-mermaid-flowchart g.node.oj-node-palette-5 circle,
+.oj-mermaid-flowchart g.node.oj-node-palette-5 path {
+ fill: #dcfce7 !important;
+ stroke: #16a34a !important;
+}
+
+.oj-mermaid-flowchart g.node .label,
+.oj-mermaid-flowchart g.node .nodeLabel,
+.oj-mermaid-flowchart g.node .nodeLabel p,
+.oj-mermaid-flowchart g.node .label span {
+ color: #0f172a !important;
+ fill: #0f172a !important;
+ font-weight: 650 !important;
+}
+
+.oj-mermaid-flowchart .edgePaths path.path,
+.oj-mermaid-flowchart .flowchart-link {
+ stroke: #64748b !important;
+ stroke-width: 2.4px !important;
+}
+
+.oj-mermaid-flowchart marker path,
+.oj-mermaid-flowchart .marker {
+ fill: #64748b !important;
+ stroke: #64748b !important;
+}
+
+.oj-mermaid-flowchart .edgeLabel rect,
+.oj-mermaid-flowchart .edgeLabel .labelBkg {
+ fill: rgba(255, 255, 255, 0.94) !important;
+ stroke: #cbd5e1 !important;
+}
+
+.oj-mermaid-flowchart .edgeLabel,
+.oj-mermaid-flowchart .edgeLabel span,
+.oj-mermaid-flowchart .edgeLabel p {
+ color: #334155 !important;
+ font-weight: 600 !important;
+}
+`
+
+const svgNamespace = "http://www.w3.org/2000/svg"
+
+function getNodeLabel(node: SVGGElement): string {
+ const el =
+ node.querySelector(".nodeLabel p") ||
+ node.querySelector(".nodeLabel") ||
+ node.querySelector(".label span") ||
+ node.querySelector(".label")
+ return el?.textContent?.trim() ?? ""
+}
+
+function applyFlowchartDisplayStyle(container: HTMLElement) {
+ container.classList.add("oj-mermaid-surface")
+
+ const svg = container.querySelector("svg")
+ if (!svg) return
+
+ svg.classList.add("oj-mermaid-flowchart")
+
+ const nodes = Array.from(svg.querySelectorAll("g.node"))
+
+ // Assign palette indices by label so same label → same color, different labels → different colors
+ const labelPaletteMap = new Map()
+ let paletteCounter = 0
+
+ nodes.forEach((node) => {
+ const hasSemanticClass = semanticNodeClasses.some((className) =>
+ node.classList.contains(className),
+ )
+ if (!hasSemanticClass) {
+ const label = getNodeLabel(node)
+ if (!labelPaletteMap.has(label)) {
+ labelPaletteMap.set(label, paletteCounter % 6)
+ paletteCounter++
+ }
+ node.classList.add(`oj-node-palette-${labelPaletteMap.get(label)}`)
+ }
+ })
+
+ svg.querySelector(`#${displayStyleId}`)?.remove()
+ const style = document.createElementNS(svgNamespace, "style")
+ style.setAttribute("id", displayStyleId)
+ style.textContent = mermaidDisplayStyle
+ svg.insertBefore(style, svg.firstChild)
+}
+
+function getChromeVersion(): number {
+ const match = navigator.userAgent.match(/Chrome\/(\d+)/)
+ return match ? parseInt(match[1]) : Infinity
+}
+
+let mermaidInstance: any = null
+let mermaidIsLegacy = false
+
+async function loadMermaid() {
+ if (!mermaidInstance) {
+ if (getChromeVersion() < 94) {
+ mermaidInstance = (await import("mermaid-legacy")).default
+ mermaidIsLegacy = true
+ } else {
+ mermaidInstance = (await import("mermaid")).default
+ }
+ mermaidInstance.initialize({
+ startOnLoad: false,
+ securityLevel: "strict",
+ theme: "base",
+ themeVariables: mermaidThemeVariables,
+ })
+ }
+ return mermaidInstance
+}
+
+export function useMermaid() {
+ const renderError = ref(null)
+ const renderSuccess = ref(false)
+ let renderGeneration = 0
+
+ const renderFlowchart = async (
+ container: HTMLElement | null,
+ mermaidCode: string,
+ ) => {
+ renderError.value = null
+ renderSuccess.value = false
+
+ if (container) container.innerHTML = ""
+
+ if (!container || !mermaidCode?.trim()) return
+
+ const gen = ++renderGeneration
+ try {
+ const m = await loadMermaid()
+ const id = `mermaid-${getRandomId()}`
+ // v9 (mermaid-legacy): callback-based render(id, code, cb)
+ // v10+: Promise-based render(id, code) → { svg }
+ const svg = mermaidIsLegacy
+ ? await new Promise((resolve, reject) => {
+ try {
+ m.render(id, mermaidCode, resolve)
+ } catch (e) {
+ reject(e)
+ }
+ })
+ : (await m.render(id, mermaidCode)).svg
+ if (gen !== renderGeneration) return
+ container.innerHTML = svg
+ applyFlowchartDisplayStyle(container)
+ renderSuccess.value = true
+ } catch (error) {
+ if (gen !== renderGeneration) return
+ renderError.value =
+ error instanceof Error
+ ? error.message
+ : "流程图渲染失败,请检查代码格式"
+ }
+ }
+
+ const clearError = () => {
+ renderError.value = null
+ }
+
+ return {
+ renderError: readonly(renderError),
+ renderSuccess: readonly(renderSuccess),
+ renderFlowchart,
+ clearError,
+ }
+}
diff --git a/apps/web/src/shared/composables/websocket.ts b/apps/web/src/shared/composables/websocket.ts
new file mode 100644
index 0000000..e267b3f
--- /dev/null
+++ b/apps/web/src/shared/composables/websocket.ts
@@ -0,0 +1,544 @@
+import { ref, onUnmounted, type Ref } from "vue"
+
+/**
+ * WebSocket 连接状态
+ */
+export type ConnectionStatus =
+ "disconnected" | "connecting" | "connected" | "error"
+
+/**
+ * WebSocket 消息类型
+ */
+export interface WebSocketMessage {
+ type: string
+ [key: string]: any
+}
+
+/**
+ * WebSocket 配置
+ */
+export interface WebSocketConfig {
+ /** WebSocket 路径(如 '/ws/submission/') */
+ path: string
+ /** 最大重连次数,默认 5 */
+ maxReconnectAttempts?: number
+ /** 重连延迟(毫秒),默认 1000 */
+ reconnectDelay?: number
+ /** 心跳间隔(毫秒),默认 30000(30秒) */
+ heartbeatTime?: number
+ /** 是否启用心跳,默认 true */
+ enableHeartbeat?: boolean
+ /** 是否启用自动重连,默认 true */
+ enableAutoReconnect?: boolean
+}
+
+/**
+ * WebSocket 消息处理器
+ */
+export type MessageHandler = (
+ data: T,
+) => void
+
+/**
+ * WebSocket 基础连接管理类
+ * 提供连接、重连、心跳等通用功能
+ */
+export class BaseWebSocket {
+ protected ws: WebSocket | null = null
+ protected url: string
+ protected handlers: Set> = new Set()
+ protected reconnectAttempts = 0
+ protected maxReconnectAttempts: number
+ protected reconnectDelay: number
+ protected heartbeatInterval: number | null = null
+ protected heartbeatTime: number
+ protected enableHeartbeat: boolean
+ protected enableAutoReconnect: boolean
+ protected disconnectTimer: number | null = null
+
+ public status: Ref = ref("disconnected")
+
+ constructor(config: WebSocketConfig) {
+ this.url = `${import.meta.env.PUBLIC_WS_URL}/${config.path}/`
+
+ this.maxReconnectAttempts = config.maxReconnectAttempts ?? 5
+ this.reconnectDelay = config.reconnectDelay ?? 1000
+ this.heartbeatTime = config.heartbeatTime ?? 30000
+ this.enableHeartbeat = config.enableHeartbeat ?? true
+ this.enableAutoReconnect = config.enableAutoReconnect ?? true
+ }
+
+ /**
+ * 连接 WebSocket
+ */
+ connect() {
+ if (
+ this.ws &&
+ (this.ws.readyState === WebSocket.OPEN ||
+ this.ws.readyState === WebSocket.CONNECTING)
+ ) {
+ return
+ }
+
+ this.status.value = "connecting"
+
+ try {
+ this.ws = new WebSocket(this.url)
+
+ this.ws.onopen = () => {
+ this.status.value = "connected"
+ this.reconnectAttempts = 0
+ console.log(`[WebSocket] 连接成功: ${this.url}`)
+ if (this.enableHeartbeat) {
+ this.startHeartbeat()
+ }
+ this.onConnected()
+ }
+
+ this.ws.onmessage = (event) => {
+ try {
+ const data = JSON.parse(event.data) as T
+ console.log(`[WebSocket] 收到消息:`, data)
+
+ // 处理心跳响应
+ if (data.type === "pong") {
+ return
+ }
+
+ // 调用消息处理钩子
+ this.onMessage(data)
+ } catch (error) {
+ console.error("[WebSocket] 解析消息失败:", error)
+ }
+ }
+
+ this.ws.onerror = (error) => {
+ console.error("[WebSocket] 连接错误:", error)
+ this.status.value = "error"
+ this.onError(error)
+ }
+
+ this.ws.onclose = (event) => {
+ console.log(
+ `[WebSocket] 连接关闭: code=${event.code}, reason=${event.reason}`,
+ )
+ this.status.value = "disconnected"
+ this.stopHeartbeat()
+ this.onDisconnected(event)
+
+ // 自动重连
+ if (
+ this.enableAutoReconnect &&
+ this.reconnectAttempts < this.maxReconnectAttempts
+ ) {
+ this.reconnectAttempts++
+ const delay = this.reconnectDelay * this.reconnectAttempts
+ console.log(
+ `[WebSocket] 将在 ${delay}ms 后重连 (尝试 ${this.reconnectAttempts}/${this.maxReconnectAttempts})`,
+ )
+ setTimeout(() => this.connect(), delay)
+ }
+ }
+ } catch (error) {
+ console.error("Failed to create WebSocket connection:", error)
+ this.status.value = "error"
+ }
+ }
+
+ /**
+ * 断开连接
+ */
+ disconnect() {
+ this.cancelScheduledDisconnect()
+ this.stopHeartbeat()
+ this.enableAutoReconnect = false // 停止自动重连
+ if (this.ws) {
+ this.ws.close()
+ this.ws = null
+ }
+ this.status.value = "disconnected"
+ }
+
+ /**
+ * 安排延迟断开连接
+ * @param delay 延迟时间(毫秒),默认 900000(15分钟)
+ */
+ scheduleDisconnect(delay: number = 15 * 60 * 1000) {
+ // 取消之前的定时器
+ this.cancelScheduledDisconnect()
+
+ // 设置新的定时器
+ this.disconnectTimer = window.setTimeout(() => {
+ const minutes = Math.floor(delay / 60000)
+ console.log(`WebSocket idle for ${minutes} minutes, disconnecting...`)
+ this.disconnect()
+ // 断开后需要重新允许自动重连
+ this.enableAutoReconnect = true
+ }, delay)
+ }
+
+ /**
+ * 取消已安排的断开连接
+ */
+ cancelScheduledDisconnect() {
+ if (this.disconnectTimer !== null) {
+ clearTimeout(this.disconnectTimer)
+ this.disconnectTimer = null
+ }
+ }
+
+ /**
+ * 发送消息
+ */
+ send(data: any) {
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
+ this.ws.send(JSON.stringify(data))
+ return true
+ }
+ return false
+ }
+
+ /**
+ * 添加消息处理器
+ */
+ addHandler(handler: MessageHandler) {
+ this.handlers.add(handler)
+ }
+
+ /**
+ * 移除消息处理器
+ */
+ removeHandler(handler: MessageHandler) {
+ this.handlers.delete(handler)
+ }
+
+ /**
+ * 清除所有处理器
+ */
+ clearHandlers() {
+ this.handlers.clear()
+ }
+
+ /**
+ * 发送心跳包
+ */
+ protected sendHeartbeat() {
+ this.send({ type: "ping", timestamp: Date.now() })
+ }
+
+ /**
+ * 开始心跳
+ */
+ protected startHeartbeat() {
+ this.stopHeartbeat()
+ this.heartbeatInterval = window.setInterval(() => {
+ this.sendHeartbeat()
+ }, this.heartbeatTime)
+ }
+
+ /**
+ * 停止心跳
+ */
+ protected stopHeartbeat() {
+ if (this.heartbeatInterval) {
+ clearInterval(this.heartbeatInterval)
+ this.heartbeatInterval = null
+ }
+ }
+
+ /**
+ * 连接成功钩子(子类可重写)
+ */
+ protected onConnected() {
+ // 子类实现
+ }
+
+ /**
+ * 断开连接钩子(子类可重写)
+ */
+ protected onDisconnected(event: CloseEvent) {
+ // 子类实现
+ }
+
+ /**
+ * 错误钩子(子类可重写)
+ */
+ protected onError(error: Event) {
+ // 子类实现
+ }
+
+ /**
+ * 消息处理钩子(子类可重写)
+ */
+ protected onMessage(data: T) {
+ // 通知所有处理器
+ this.handlers.forEach((handler) => {
+ try {
+ handler(data)
+ } catch (error) {
+ console.error("Error in message handler:", error)
+ }
+ })
+ }
+}
+
+/**
+ * 提交状态更新的数据类型
+ */
+export interface SubmissionUpdate extends WebSocketMessage {
+ type: "submission_update"
+ submission_id: string
+ result: number
+ status: "pending" | "judging" | "finished" | "error"
+ time_cost?: number
+ memory_cost?: number
+ score?: number
+ err_info?: string
+}
+
+/**
+ * 提交 WebSocket 连接管理类
+ */
+class SubmissionWebSocket extends BaseWebSocket {
+ constructor() {
+ super({
+ path: "submission",
+ })
+ }
+
+ /**
+ * 订阅特定提交的更新
+ */
+ subscribe(submissionId: string) {
+ const success = this.send({
+ type: "subscribe",
+ submission_id: submissionId,
+ })
+ if (!success) {
+ console.error("[WebSocket] 订阅失败: 连接未就绪")
+ }
+ }
+}
+
+/**
+ * 用于组件中使用 WebSocket 的 Composable
+ * 每次调用创建新的 WebSocket 实例
+ */
+export function useSubmissionWebSocket(
+ handler?: MessageHandler,
+) {
+ const ws = new SubmissionWebSocket()
+
+ // 如果提供了处理器,添加到实例中
+ if (handler) {
+ ws.addHandler(handler)
+ }
+
+ // 组件卸载时清理资源
+ onUnmounted(() => {
+ if (handler) {
+ ws.removeHandler(handler)
+ }
+ ws.disconnect()
+ })
+
+ return {
+ connect: () => ws.connect(),
+ disconnect: () => ws.disconnect(),
+ subscribe: (submissionId: string) => ws.subscribe(submissionId),
+ scheduleDisconnect: (delay?: number) => ws.scheduleDisconnect(delay),
+ cancelScheduledDisconnect: () => ws.cancelScheduledDisconnect(),
+ status: ws.status,
+ addHandler: (h: MessageHandler) => ws.addHandler(h),
+ removeHandler: (h: MessageHandler) => ws.removeHandler(h),
+ }
+}
+
+/**
+ * 通用 WebSocket Composable 工厂函数
+ * 用于创建自定义的 WebSocket composable
+ *
+ * @example
+ * ```ts
+ * // 创建通知 WebSocket
+ * interface NotificationMessage extends WebSocketMessage {
+ * type: 'notification'
+ * title: string
+ * content: string
+ * }
+ *
+ * class NotificationWebSocket extends BaseWebSocket {
+ * constructor() {
+ * super({ path: 'notification' })
+ * }
+ * }
+ *
+ * let notificationWs: NotificationWebSocket | null = null
+ *
+ * export function useNotificationWebSocket(handler?: MessageHandler) {
+ * if (!notificationWs) {
+ * notificationWs = new NotificationWebSocket()
+ * }
+ * return createWebSocketComposable(notificationWs, handler)
+ * }
+ * ```
+ */
+export function createWebSocketComposable(
+ ws: BaseWebSocket,
+ handler?: MessageHandler,
+) {
+ if (handler) {
+ ws.addHandler(handler)
+ }
+
+ onUnmounted(() => {
+ if (handler) {
+ ws.removeHandler(handler)
+ }
+ })
+
+ return {
+ connect: () => ws.connect(),
+ disconnect: () => ws.disconnect(),
+ send: (data: any) => ws.send(data),
+ status: ws.status,
+ addHandler: (h: MessageHandler) => ws.addHandler(h),
+ removeHandler: (h: MessageHandler) => ws.removeHandler(h),
+ }
+}
+
+/**
+ * 流程图评分更新消息类型
+ */
+export interface FlowchartEvaluationUpdate extends WebSocketMessage {
+ type:
+ | "flowchart_evaluation_completed"
+ | "flowchart_evaluation_failed"
+ | "flowchart_evaluation_update"
+ submission_id: string
+ score?: number
+ grade?: string
+ feedback?: string
+ suggestions?: string
+ criteriaDetails?: any
+ error?: string
+}
+
+/**
+ * 流程图 WebSocket 连接管理类
+ */
+class FlowchartWebSocket extends BaseWebSocket {
+ constructor() {
+ super({
+ path: "flowchart", // 使用专门的 flowchart WebSocket 路径
+ })
+ }
+
+ /**
+ * 订阅特定流程图提交的更新
+ */
+ subscribe(submissionId: string) {
+ const success = this.send({
+ type: "subscribe",
+ submission_id: submissionId,
+ })
+ if (!success) {
+ console.error("[Flowchart WebSocket] 订阅失败: 连接未就绪")
+ }
+ }
+}
+
+/**
+ * 用于组件中使用流程图 WebSocket 的 Composable
+ */
+export function useFlowchartWebSocket(
+ handler?: MessageHandler,
+) {
+ const ws = new FlowchartWebSocket()
+
+ // 如果提供了处理器,添加到实例中
+ if (handler) {
+ ws.addHandler(handler)
+ }
+
+ // 组件卸载时清理资源
+ onUnmounted(() => {
+ if (handler) {
+ ws.removeHandler(handler)
+ }
+ ws.disconnect()
+ })
+
+ return {
+ connect: () => ws.connect(),
+ disconnect: () => ws.disconnect(),
+ subscribe: (submissionId: string) => ws.subscribe(submissionId),
+ scheduleDisconnect: (delay?: number) => ws.scheduleDisconnect(delay),
+ cancelScheduledDisconnect: () => ws.cancelScheduledDisconnect(),
+ status: ws.status,
+ addHandler: (h: MessageHandler) =>
+ ws.addHandler(h),
+ removeHandler: (h: MessageHandler) =>
+ ws.removeHandler(h),
+ }
+}
+
+/**
+ * 配置更新消息类型
+ */
+export interface ConfigUpdate extends WebSocketMessage {
+ type: "config_update"
+ key: string
+ value: any
+}
+
+/**
+ * 配置 WebSocket 连接管理类
+ */
+class ConfigWebSocket extends BaseWebSocket {
+ constructor() {
+ super({
+ path: "config",
+ })
+ }
+
+ /**
+ * 发送配置更新
+ */
+ updateConfig(key: string, value: any) {
+ this.send({
+ type: "config_update",
+ key,
+ value,
+ })
+ }
+}
+
+/**
+ * 用于组件中使用配置 WebSocket 的 Composable
+ */
+export function useConfigWebSocket(handler?: MessageHandler) {
+ const ws = new ConfigWebSocket()
+
+ onMounted(() => {
+ if (handler) {
+ ws.addHandler(handler)
+ }
+ })
+
+ onUnmounted(() => {
+ if (handler) {
+ ws.removeHandler(handler)
+ }
+ ws.disconnect()
+ })
+
+ return {
+ connect: () => ws.connect(),
+ disconnect: () => ws.disconnect(),
+ updateConfig: (key: string, value: any) => ws.updateConfig(key, value),
+ status: ws.status,
+ addHandler: (h: MessageHandler) => ws.addHandler(h),
+ removeHandler: (h: MessageHandler) => ws.removeHandler(h),
+ }
+}
diff --git a/apps/web/src/shared/extensions/autocompletion.ts b/apps/web/src/shared/extensions/autocompletion.ts
new file mode 100644
index 0000000..ba77d68
--- /dev/null
+++ b/apps/web/src/shared/extensions/autocompletion.ts
@@ -0,0 +1,107 @@
+import type {
+ Completion,
+ CompletionContext,
+ CompletionResult,
+ CompletionSource,
+} from "@codemirror/autocomplete"
+import type { EditorView } from "@codemirror/view"
+import { useProblemStore } from "oj/store/problem"
+import type { LANGUAGE } from "utils/types"
+import { c } from "./c"
+import { python } from "./python"
+import { sql } from "./sql"
+
+type ChineseCompletion = Pick<
+ Completion,
+ "label" | "detail" | "type" | "info" | "boost" | "apply"
+> & { apply?: string | Completion["apply"] }
+
+// 中文注释提示
+const chineseAnnotations: Record = {
+ python,
+ c,
+ sql,
+}
+
+// SQL 题:当前题目的表名和字段名补全,数据来自 sql_display
+function sqlSchemaCompletions(): Completion[] {
+ const tables = useProblemStore().problem?.sql_display?.tables ?? []
+ return tables.flatMap((table) => [
+ {
+ label: table.name,
+ detail: "数据表",
+ type: "class",
+ info: `字段:${table.columns
+ .map((col) => (col.type ? `${col.name} ${col.type}` : col.name))
+ .join(", ")}`,
+ boost: 110,
+ },
+ ...table.columns.map((col) => ({
+ label: col.name,
+ detail: col.type
+ ? `${table.name} 的字段 · ${col.type}`
+ : `${table.name} 的字段`,
+ type: "property",
+ boost: 105,
+ })),
+ ])
+}
+
+export function enhanceCompletion(language: LANGUAGE): CompletionSource {
+ return async function (
+ context: CompletionContext,
+ ): Promise {
+ const word = context.matchBefore(/\w+/)
+ if (!word && !context.explicit) return null
+
+ const trulyLanguage =
+ language === "SQL"
+ ? "sql"
+ : language.startsWith("Python")
+ ? "python"
+ : "c"
+ const completions: Completion[] = (
+ chineseAnnotations[trulyLanguage] || []
+ ).map((completion) => {
+ const insertText =
+ typeof completion.apply === "string"
+ ? completion.apply
+ : completion.label
+ const cursorOffset = insertText.includes("(")
+ ? insertText.indexOf("(") + 1
+ : insertText.length
+
+ if (
+ (completion.type === "function" || completion.type === "method") &&
+ insertText.includes(")")
+ ) {
+ return {
+ ...completion,
+ apply: (
+ view: EditorView,
+ _c: Completion,
+ from: number,
+ to: number,
+ ) => {
+ view.dispatch({
+ changes: { from, to, insert: insertText },
+ selection: { anchor: from + cursorOffset },
+ })
+ },
+ }
+ }
+
+ return completion
+ })
+
+ if (trulyLanguage === "sql") {
+ completions.push(...sqlSchemaCompletions())
+ }
+
+ return {
+ from: word ? word.from : context.pos,
+ options: completions,
+ validFor: /^\w+$/,
+ }
+ }
+}
diff --git a/apps/web/src/shared/extensions/baseTheme.ts b/apps/web/src/shared/extensions/baseTheme.ts
new file mode 100644
index 0000000..f663018
--- /dev/null
+++ b/apps/web/src/shared/extensions/baseTheme.ts
@@ -0,0 +1,9 @@
+import { EditorView } from "@codemirror/view"
+
+export const styleTheme = EditorView.baseTheme({
+ "& .cm-scroller": { "font-family": "Monaco" },
+ "&.cm-editor.cm-focused": { outline: "none" },
+ "&.cm-editor .cm-tooltip.cm-tooltip-autocomplete ul": {
+ "font-family": "Monaco",
+ },
+})
diff --git a/apps/web/src/shared/extensions/c.ts b/apps/web/src/shared/extensions/c.ts
new file mode 100644
index 0000000..3b1236b
--- /dev/null
+++ b/apps/web/src/shared/extensions/c.ts
@@ -0,0 +1,257 @@
+export const c = [
+ {
+ label: "printf",
+ detail: "格式化输出",
+ type: "function",
+ info: "标准输出函数,按格式打印,搭配 %d/%s 等占位符",
+ boost: 90,
+ apply: "printf();",
+ },
+ {
+ label: "scanf",
+ detail: "格式化输入",
+ type: "function",
+ info: "标准输入函数,按格式读取,用 & 取地址接收变量",
+ boost: 88,
+ apply: "scanf();",
+ },
+ {
+ label: "puts",
+ detail: "输出字符串",
+ type: "function",
+ info: "输出以 \\0 结尾的字符串并自动换行,比 printf 简洁",
+ boost: 84,
+ apply: "puts();",
+ },
+ {
+ label: "gets",
+ detail: "读取字符串",
+ type: "function",
+ info: "读取一行字符串到缓冲区(不安全,建议使用 fgets)",
+ boost: 60,
+ apply: "gets();",
+ },
+ {
+ label: "fgets",
+ detail: "安全读行",
+ type: "function",
+ info: "从文件流读取一行到缓冲区,限制长度,避免溢出",
+ boost: 82,
+ apply: "fgets();",
+ },
+ {
+ label: "memset",
+ detail: "内存填充",
+ type: "function",
+ info: "按字节把内存填为指定值,常用于初始化数组或结构体",
+ boost: 80,
+ apply: "memset();",
+ },
+ {
+ label: "memcpy",
+ detail: "内存拷贝",
+ type: "function",
+ info: "从源地址复制指定字节到目标地址,避免重叠",
+ boost: 78,
+ apply: "memcpy();",
+ },
+ {
+ label: "strlen",
+ detail: "字符串长度",
+ type: "function",
+ info: "计算以 \\0 结尾的字符串长度(不含终止符)",
+ boost: 76,
+ apply: "strlen();",
+ },
+ {
+ label: "strcmp",
+ detail: "字符串比较",
+ type: "function",
+ info: "按字典序比较两个字符串,相等为 0,小于返回负数",
+ boost: 74,
+ apply: "strcmp();",
+ },
+ {
+ label: "strcpy",
+ detail: "字符串拷贝",
+ type: "function",
+ info: "把源字符串复制到目标(含终止符),目标要有足够空间",
+ boost: 72,
+ apply: "strcpy();",
+ },
+ {
+ label: "int main",
+ detail: "程序入口",
+ type: "keyword",
+ info: "C 程序入口,通常返回 0 表示正常退出",
+ boost: 70,
+ },
+ {
+ label: "for",
+ detail: "循环语句",
+ type: "keyword",
+ info: "for (init; condition; step) 结构,用于固定次数循环",
+ boost: 68,
+ },
+ {
+ label: "while",
+ detail: "条件循环",
+ type: "keyword",
+ info: "while (condition) 条件循环,条件真则执行",
+ boost: 66,
+ },
+ {
+ label: "if",
+ detail: "条件判断",
+ type: "keyword",
+ info: "if (condition) 条件分支,可配 else/else if",
+ boost: 64,
+ },
+ {
+ label: "struct",
+ detail: "结构体定义",
+ type: "keyword",
+ info: "定义结构体,可组合不同类型的成员",
+ boost: 62,
+ },
+ {
+ label: "typedef",
+ detail: "类型别名",
+ type: "keyword",
+ info: "为已有类型起别名,提升可读性",
+ boost: 60,
+ },
+ {
+ label: "const",
+ detail: "只读限定",
+ type: "keyword",
+ info: "声明常量或只读指针,防止被修改",
+ boost: 58,
+ },
+ {
+ label: "return",
+ detail: "返回值",
+ type: "keyword",
+ info: "结束函数并返回值,main 返回 0 表示成功",
+ boost: 56,
+ },
+ {
+ label: "break",
+ detail: "终止循环",
+ type: "keyword",
+ info: "立即退出最近的 for/while/do 循环体",
+ boost: 54,
+ },
+ {
+ label: "continue",
+ detail: "继续下一次循环",
+ type: "keyword",
+ info: "跳过当前循环余下语句,开始下一轮迭代",
+ boost: 52,
+ },
+ {
+ label: "do",
+ detail: "do-while 循环",
+ type: "keyword",
+ info: "do { ... } while(condition); 后置条件循环,至少执行一次",
+ boost: 50,
+ },
+ {
+ label: "else",
+ detail: "分支兜底",
+ type: "keyword",
+ info: "if/else if/else 结构中的兜底分支",
+ boost: 48,
+ },
+ {
+ label: "switch",
+ detail: "多分支选择",
+ type: "keyword",
+ info: "switch (expr) { case ... } 多分支选择结构,常配合 break",
+ boost: 46,
+ },
+ {
+ label: "case",
+ detail: "分支标签",
+ type: "keyword",
+ info: "switch 中的具体匹配分支,常在末尾使用 break",
+ boost: 44,
+ },
+ {
+ label: "default",
+ detail: "默认分支",
+ type: "keyword",
+ info: "switch 中未匹配任何 case 时执行的分支",
+ boost: 42,
+ },
+ {
+ label: "goto",
+ detail: "无条件跳转",
+ type: "keyword",
+ info: "跳转到指定标签位置,需谨慎使用以避免可读性问题",
+ boost: 40,
+ },
+ {
+ label: "sizeof",
+ detail: "求字节大小",
+ type: "keyword",
+ info: "返回类型或表达式所占字节数,编译期求值",
+ boost: 38,
+ },
+ {
+ label: "int",
+ detail: "整数类型",
+ type: "keyword",
+ info: "声明整型变量或返回值,通常占 4 字节",
+ boost: 36,
+ },
+ {
+ label: "char",
+ detail: "字符类型",
+ type: "keyword",
+ info: "声明字符或字节型变量,通常占 1 字节",
+ boost: 34,
+ },
+ {
+ label: "double",
+ detail: "双精度类型",
+ type: "keyword",
+ info: "声明双精度浮点变量,通常占 8 字节",
+ boost: 32,
+ },
+ {
+ label: "float",
+ detail: "单精度类型",
+ type: "keyword",
+ info: "声明单精度浮点变量,通常占 4 字节",
+ boost: 30,
+ },
+ {
+ label: "void",
+ detail: "空类型",
+ type: "keyword",
+ info: "表示无返回值或无类型指针(void*)",
+ boost: 28,
+ },
+ {
+ label: "unsigned",
+ detail: "无符号修饰",
+ type: "keyword",
+ info: "与整型/字符型组合表示无符号数,扩大正数范围",
+ boost: 26,
+ },
+ {
+ label: "long",
+ detail: "长整型修饰",
+ type: "keyword",
+ info: "与整型组合表示更大范围(long/long long)",
+ boost: 24,
+ },
+ {
+ label: "short",
+ detail: "短整型修饰",
+ type: "keyword",
+ info: "与整型组合表示较小范围的整数类型",
+ boost: 22,
+ },
+]
diff --git a/apps/web/src/shared/extensions/python.ts b/apps/web/src/shared/extensions/python.ts
new file mode 100644
index 0000000..e93182d
--- /dev/null
+++ b/apps/web/src/shared/extensions/python.ts
@@ -0,0 +1,608 @@
+export const python = [
+ {
+ label: "print",
+ detail: "打印输出",
+ type: "function",
+ info: "最常用的输出函数,把内容打印到屏幕;sep 控制分隔符,end 控制行尾。",
+ boost: 100,
+ apply: "print()",
+ },
+ {
+ label: "input",
+ detail: "读取输入",
+ type: "function",
+ info: "读取一行输入并返回字符串,可以传入提示文字。",
+ boost: 99,
+ apply: "input()",
+ },
+ {
+ label: "len",
+ detail: "获取长度",
+ type: "function",
+ info: "返回序列或集合的长度,常用来统计列表、字符串中有多少个元素。",
+ boost: 90,
+ apply: "len()",
+ },
+ {
+ label: "range",
+ detail: "生成整数序列",
+ type: "function",
+ info: "生成整数序列,支持起点、终点和步长,常配合 for 循环遍历次数。",
+ boost: 85,
+ apply: "range()",
+ },
+ {
+ label: "enumerate",
+ detail: "枚举索引与值",
+ type: "function",
+ info: "遍历时同时得到索引和值,可用 start 指定起始编号,适合需要编号输出的场景。",
+ boost: 82,
+ apply: "enumerate()",
+ },
+ {
+ label: "zip",
+ detail: "并行遍历",
+ type: "function",
+ info: "把多个可迭代对象按位置打包成元组并行遍历,长度取最短的序列。",
+ boost: 80,
+ apply: "zip()",
+ },
+ {
+ label: "map",
+ detail: "映射函数",
+ type: "function",
+ info: "把函数作用到序列每个元素,返回惰性迭代器,需要 list() 展开后才能看到结果。",
+ boost: 78,
+ apply: "map()",
+ },
+ {
+ label: "filter",
+ detail: "过滤元素",
+ type: "function",
+ info: "保留函数返回真值的元素,返回惰性迭代器,常用于筛选出符合条件的数据。",
+ boost: 76,
+ apply: "filter()",
+ },
+ {
+ label: "sorted",
+ detail: "排序",
+ type: "function",
+ info: "返回排好序的新列表,支持 key 排序函数和 reverse 逆序,不会修改原序列。",
+ boost: 74,
+ apply: "sorted()",
+ },
+ {
+ label: "sum",
+ detail: "求和",
+ type: "function",
+ info: "对可迭代对象求和,可设置初始值,常用于数字累计与前缀和。",
+ boost: 72,
+ apply: "sum()",
+ },
+ {
+ label: "open",
+ detail: "文件读写",
+ type: "function",
+ info: "打开文件获得文件对象,常配合 with 自动关闭,支持读写追加等多种模式。",
+ boost: 70,
+ apply: "open()",
+ },
+ {
+ label: "abs",
+ detail: "绝对值",
+ type: "function",
+ info: "返回数字的绝对值,把负数变成正数,常用于距离或差值计算。",
+ boost: 68,
+ apply: "abs()",
+ },
+ {
+ label: "round",
+ detail: "四舍五入",
+ type: "function",
+ info: "按指定小数位四舍五入,默认保留到整数,适合处理成绩或金额保留位数。",
+ boost: 66,
+ apply: "round()",
+ },
+ {
+ label: "isinstance",
+ detail: "类型检查",
+ type: "function",
+ info: "判断对象是否属于某个类型或类型元组,常用于分支处理不同数据。",
+ boost: 64,
+ apply: "isinstance()",
+ },
+ {
+ label: "type",
+ detail: "获取类型",
+ type: "function",
+ info: "返回对象的类型,或用三个参数动态创建类型,用来了解变量真实类型。",
+ boost: 62,
+ apply: "type()",
+ },
+ {
+ label: "list",
+ detail: "列表构造",
+ type: "function",
+ info: "把可迭代对象转换为列表,或创建空列表存放数据,支持列表推导式。",
+ boost: 60,
+ apply: "list()",
+ },
+ {
+ label: "dict",
+ detail: "字典构造",
+ type: "function",
+ info: "根据映射或键值对序列创建字典,用来存储键值对信息。",
+ boost: 58,
+ apply: "dict()",
+ },
+ {
+ label: "set",
+ detail: "集合构造",
+ type: "function",
+ info: "把可迭代对象转换为集合,自动去重,适合判重和集合运算。",
+ boost: 56,
+ apply: "set()",
+ },
+ {
+ label: "tuple",
+ detail: "元组构造",
+ type: "function",
+ info: "把可迭代对象转换为元组,或创建不可变的序列,用作安全的组合数据。",
+ boost: 54,
+ apply: "tuple()",
+ },
+ {
+ label: "int",
+ detail: "转整数",
+ type: "function",
+ info: "把参数转为整数,支持进制转换,如 int('101', 2) 表示二进制转十进制。",
+ boost: 74,
+ apply: "int()",
+ },
+ {
+ label: "float",
+ detail: "转浮点数",
+ type: "function",
+ info: "把参数转为浮点数,接受字符串或数字,常用于保留小数的计算。",
+ boost: 72,
+ apply: "float()",
+ },
+ {
+ label: "str",
+ detail: "转字符串",
+ type: "function",
+ info: "把对象转为字符串,常用于输出、拼接或写入文件。",
+ boost: 70,
+ apply: "str()",
+ },
+ {
+ label: "bool",
+ detail: "转布尔值",
+ type: "function",
+ info: "按真值规则转为 True/False,空对象一般为 False,常用于条件判断。",
+ boost: 68,
+ apply: "bool()",
+ },
+ {
+ label: "def",
+ detail: "定义函数",
+ type: "keyword",
+ info: "定义函数,可写位置参数、关键字参数和默认值,是封装和复用代码的基础。",
+ boost: 52,
+ },
+ {
+ label: "class",
+ detail: "定义类",
+ type: "keyword",
+ info: "定义类,支持继承和魔术方法,用来创建自定义数据类型。",
+ boost: 50,
+ },
+ {
+ label: "with",
+ detail: "上下文管理",
+ type: "keyword",
+ info: "进入上下文管理器,自动处理进入与退出,常用于文件、锁等需要收尾的资源。",
+ boost: 48,
+ },
+ {
+ label: "try",
+ detail: "异常捕获",
+ type: "keyword",
+ info: "开始异常处理代码块,后面接 except/finally/else,防止程序因错误直接退出。",
+ boost: 46,
+ },
+ {
+ label: "except",
+ detail: "处理异常",
+ type: "keyword",
+ info: "捕获并处理指定异常,与 try 连用,让程序能优雅处理输入或运行时错误。",
+ boost: 44,
+ },
+ {
+ label: "finally",
+ detail: "收尾",
+ type: "keyword",
+ info: "无论是否出现异常都会执行的收尾代码块,常用于关闭文件或释放资源。",
+ boost: 42,
+ },
+ {
+ label: "import",
+ detail: "导入模块",
+ type: "keyword",
+ info: "导入模块或包中的名称,可用 as 取别名,便于使用标准库或第三方库。",
+ boost: 40,
+ },
+ {
+ label: "from",
+ detail: "按需导入",
+ type: "keyword",
+ info: "从模块中按名称导入对象,可与 import/as 组合,减少书写模块前缀。",
+ boost: 38,
+ },
+ {
+ label: "return",
+ detail: "返回值",
+ type: "keyword",
+ info: "结束函数并返回一个值,不写时默认返回 None。",
+ boost: 36,
+ },
+ {
+ label: "for",
+ detail: "for 循环",
+ type: "keyword",
+ info: "逐项遍历,可以配合 range 使用。",
+ boost: 45,
+ },
+ {
+ label: "while",
+ detail: "while 循环",
+ type: "keyword",
+ info: "条件循环,条件为真时执行,支持 break/continue 和 else 分支。",
+ boost: 43,
+ },
+ {
+ label: "if",
+ detail: "条件分支",
+ type: "keyword",
+ info: "if 判断,条件成立执行对应语句,可与 elif/else 组成多分支。",
+ boost: 41,
+ apply: "if ",
+ },
+ {
+ label: "elif",
+ detail: "多分支判断",
+ type: "keyword",
+ info: "否则如果,用来依次判断多个条件,让逻辑更清晰。",
+ boost: 39,
+ apply: "elif ",
+ },
+ {
+ label: "else",
+ detail: "否则",
+ type: "keyword",
+ info: "否则,在前面条件不满足或循环未提前退出时执行。",
+ boost: 37,
+ apply: "else:",
+ },
+ {
+ label: "break",
+ detail: "退出循环",
+ type: "keyword",
+ info: "立即终止最近的循环(for 或 while),跳出当前循环体。",
+ boost: 35,
+ },
+ {
+ label: "continue",
+ detail: "跳过本次",
+ type: "keyword",
+ info: "结束本轮循环迭代,直接开始下一轮条件判断。",
+ boost: 33,
+ },
+ {
+ label: "pass",
+ detail: "空操作占位",
+ type: "keyword",
+ info: "占位语句,不执行任何操作,常用于占位或编写空的函数/类体。",
+ boost: 31,
+ },
+ {
+ label: "lambda",
+ detail: "匿名函数",
+ type: "keyword",
+ info: "定义轻量级匿名函数,语法 lambda 参数: 表达式,返回表达式结果,适合简短逻辑。",
+ boost: 29,
+ },
+ {
+ label: "yield",
+ detail: "生成器产出",
+ type: "keyword",
+ info: "在函数中产出一个值并暂停状态,把函数变成生成器,可配合 yield from 继续产出。",
+ boost: 27,
+ },
+ {
+ label: "global",
+ detail: "声明全局变量",
+ type: "keyword",
+ info: "在函数内部声明写入模块级变量的权限,用于修改外层的全局变量。",
+ boost: 25,
+ },
+ {
+ label: "nonlocal",
+ detail: "声明外层变量",
+ type: "keyword",
+ info: "在嵌套函数中声明使用最近外层作用域的变量,便于修改闭包变量。",
+ boost: 23,
+ },
+ {
+ label: "True",
+ detail: "布尔真",
+ type: "keyword",
+ info: "布尔常量 True,与 False/None 一起常用于条件表达式和逻辑判断。",
+ boost: 21,
+ },
+ {
+ label: "False",
+ detail: "布尔假",
+ type: "keyword",
+ info: "布尔常量 False,在逻辑判断中表示假值,常作为条件的否定结果。",
+ boost: 19,
+ },
+ {
+ label: "None",
+ detail: "空值对象",
+ type: "keyword",
+ info: "表示空值或缺失值的单例对象,常用于默认参数或占位。",
+ boost: 17,
+ },
+ {
+ label: "and",
+ detail: "逻辑与",
+ type: "keyword",
+ info: "逻辑与运算符,短路求值,返回最后被求值的操作数,常用于多条件同时成立。",
+ boost: 15,
+ },
+ {
+ label: "or",
+ detail: "逻辑或",
+ type: "keyword",
+ info: "逻辑或运算符,短路求值,返回第一个真值或最后一个操作数,常用于设默认值。",
+ boost: 14,
+ },
+ {
+ label: "not",
+ detail: "逻辑非",
+ type: "keyword",
+ info: "逻辑非运算符,返回布尔取反结果,把真变假、假变真。",
+ boost: 13,
+ },
+ {
+ label: "in",
+ detail: "成员测试",
+ type: "keyword",
+ info: "判断元素是否在序列、集合或字典中,返回布尔结果,常用于查找。",
+ boost: 12,
+ },
+ {
+ label: "is",
+ detail: "身份比较",
+ type: "keyword",
+ info: "比较两个对象是否是同一个对象,常用于和 None 比较以避免误判。",
+ boost: 11,
+ },
+ {
+ label: "append",
+ detail: "列表追加",
+ type: "method",
+ info: "在列表尾部添加一个新元素,等价于 list.append(value)。",
+ boost: 48,
+ apply: "append()",
+ },
+ {
+ label: "insert",
+ detail: "列表插入",
+ type: "method",
+ info: "在指定位置插入元素,语法 list.insert(index, value)。",
+ boost: 46,
+ apply: "insert()",
+ },
+ {
+ label: "remove",
+ detail: "删除匹配值",
+ type: "method",
+ info: "删除列表中第一次出现的指定值,不存在会抛出异常 ValueError。",
+ boost: 44,
+ apply: "remove()",
+ },
+ {
+ label: "pop",
+ detail: "弹出元素",
+ type: "method",
+ info: "移除并返回列表指定位置(默认尾部)的元素,用于栈或队列操作。",
+ boost: 42,
+ apply: "pop()",
+ },
+ {
+ label: "count",
+ detail: "统计次数",
+ type: "method",
+ info: "返回某个对象在列表中出现的次数,常用于频次统计。",
+ boost: 40,
+ apply: "count()",
+ },
+ {
+ label: "reverse",
+ detail: "反转列表",
+ type: "method",
+ info: "原地反转列表中元素的顺序,常与切片 [::-1] 效果类似。",
+ boost: 38,
+ apply: "reverse()",
+ },
+ {
+ label: "sort",
+ detail: "列表排序",
+ type: "method",
+ info: "对列表进行原地排序,可指定 key 排序函数和 reverse 是否倒序。",
+ boost: 36,
+ apply: "sort()",
+ },
+ {
+ label: "add",
+ detail: "集合添加",
+ type: "method",
+ info: "向集合添加单个元素,若元素已存在则忽略,保持集合去重特性。",
+ boost: 50,
+ apply: "add()",
+ },
+ {
+ label: "clear",
+ detail: "清空集合",
+ type: "method",
+ info: "移除集合中所有元素,变成一个空集合。",
+ boost: 48,
+ apply: "clear()",
+ },
+ {
+ label: "keys",
+ detail: "字典键",
+ type: "method",
+ info: "返回字典键的可迭代视图,用于遍历所有键。",
+ boost: 42,
+ apply: "keys()",
+ },
+ {
+ label: "values",
+ detail: "字典值",
+ type: "method",
+ info: "返回字典值的可迭代视图,用于遍历所有值。",
+ boost: 40,
+ apply: "values()",
+ },
+ {
+ label: "split",
+ detail: "字符串切分",
+ type: "method",
+ info: "按分隔符切分字符串,返回列表,默认按空白字符分割。",
+ boost: 52,
+ apply: "split()",
+ },
+ {
+ label: "replace",
+ detail: "字符串替换",
+ type: "method",
+ info: "把字符串中的子串替换为新内容,可限制替换次数。",
+ boost: 50,
+ apply: "replace()",
+ },
+ {
+ label: "format",
+ detail: "格式化字符串",
+ type: "method",
+ info: "使用占位符或命名参数进行字符串格式化,便于拼接变量输出。",
+ boost: 48,
+ apply: "format()",
+ },
+ {
+ label: "strip",
+ detail: "去首尾字符",
+ type: "method",
+ info: "移除字符串首尾指定字符,默认移除空白符,常用于清理输入。",
+ boost: 46,
+ apply: "strip()",
+ },
+ {
+ label: "lower",
+ detail: "转小写",
+ type: "method",
+ info: "把字符串中的字母转换为小写形式,常用于不区分大小写比较。",
+ boost: 44,
+ apply: "lower()",
+ },
+ {
+ label: "upper",
+ detail: "转大写",
+ type: "method",
+ info: "把字符串中的字母转换为大写形式。",
+ boost: 42,
+ apply: "upper()",
+ },
+ {
+ label: "swapcase",
+ detail: "大小写互换",
+ type: "method",
+ info: "把字符串中的大小写字母互换,便于切换展示风格。",
+ boost: 40,
+ apply: "swapcase()",
+ },
+ {
+ label: "find",
+ detail: "查找子串位置",
+ type: "method",
+ info: "返回子串首次出现的索引,未找到返回 -1,适合安全查找。",
+ boost: 38,
+ apply: "find()",
+ },
+ {
+ label: "index",
+ detail: "查找子串索引",
+ type: "method",
+ info: "返回子串首次出现的索引,未找到会抛出异常 ValueError。",
+ boost: 36,
+ apply: "index()",
+ },
+ {
+ label: "startswith",
+ detail: "前缀判断",
+ type: "method",
+ info: "判断字符串是否以指定前缀开头,可指定检查的范围切片。",
+ boost: 34,
+ apply: "startswith()",
+ },
+ {
+ label: "endswith",
+ detail: "后缀判断",
+ type: "method",
+ info: "判断字符串是否以指定后缀结尾,可指定范围,常用于文件名处理。",
+ boost: 32,
+ apply: "endswith()",
+ },
+ {
+ label: "isalnum",
+ detail: "是否字母数字",
+ type: "method",
+ info: "检测字符串是否只由字母和数字组成,常用于基础输入校验。",
+ boost: 30,
+ apply: "isalnum()",
+ },
+ {
+ label: "isalpha",
+ detail: "是否字母",
+ type: "method",
+ info: "检测字符串是否只由字母组成,用来判断名字等只含字母的场景。",
+ boost: 28,
+ apply: "isalpha()",
+ },
+ {
+ label: "isdigit",
+ detail: "是否数字",
+ type: "method",
+ info: "检测字符串是否只由数字组成,用来判断输入是否为纯数字。",
+ boost: 26,
+ apply: "isdigit()",
+ },
+ {
+ label: "islower",
+ detail: "是否全小写",
+ type: "method",
+ info: "检测字符串是否全部由小写字母组成且至少有一个字母。",
+ boost: 24,
+ apply: "islower()",
+ },
+ {
+ label: "isupper",
+ detail: "是否全大写",
+ type: "method",
+ info: "检测字符串中所有字母是否都是大写且至少有一个字母。",
+ boost: 22,
+ apply: "isupper()",
+ },
+]
diff --git a/apps/web/src/shared/extensions/sql.ts b/apps/web/src/shared/extensions/sql.ts
new file mode 100644
index 0000000..dfc79ac
--- /dev/null
+++ b/apps/web/src/shared/extensions/sql.ts
@@ -0,0 +1,404 @@
+export const sql = [
+ {
+ label: "SELECT",
+ detail: "查询数据",
+ type: "keyword",
+ info: "从表中查询数据,后面接要查询的列名,用 * 表示所有列。",
+ boost: 100,
+ apply: "SELECT ",
+ },
+ {
+ label: "FROM",
+ detail: "指定表",
+ type: "keyword",
+ info: "指定要查询的表,和 SELECT 搭配使用,如 SELECT * FROM 表名。",
+ boost: 98,
+ apply: "FROM ",
+ },
+ {
+ label: "WHERE",
+ detail: "筛选条件",
+ type: "keyword",
+ info: "按条件筛选行,只保留满足条件的数据,如 WHERE age > 18。",
+ boost: 96,
+ apply: "WHERE ",
+ },
+ {
+ label: "ORDER BY",
+ detail: "排序",
+ type: "keyword",
+ info: "按指定列排序,默认从小到大(ASC),加 DESC 表示从大到小。",
+ boost: 90,
+ apply: "ORDER BY ",
+ },
+ {
+ label: "GROUP BY",
+ detail: "分组",
+ type: "keyword",
+ info: "按指定列分组,常配合 COUNT、SUM 等聚合函数统计每组数据。",
+ boost: 88,
+ apply: "GROUP BY ",
+ },
+ {
+ label: "HAVING",
+ detail: "分组后筛选",
+ type: "keyword",
+ info: "对分组后的结果再筛选,WHERE 筛选行,HAVING 筛选组。",
+ boost: 86,
+ apply: "HAVING ",
+ },
+ {
+ label: "LIMIT",
+ detail: "限制条数",
+ type: "keyword",
+ info: "限制返回的行数,如 LIMIT 5 只取前 5 条,常和 ORDER BY 搭配。",
+ boost: 84,
+ apply: "LIMIT ",
+ },
+ {
+ label: "DISTINCT",
+ detail: "去重",
+ type: "keyword",
+ info: "去掉查询结果中的重复值,如 SELECT DISTINCT city FROM users。",
+ boost: 82,
+ apply: "DISTINCT ",
+ },
+ {
+ label: "AS",
+ detail: "起别名",
+ type: "keyword",
+ info: "给列或表起别名,让结果更易读,如 SELECT name AS 姓名。",
+ boost: 80,
+ apply: "AS ",
+ },
+ {
+ label: "JOIN",
+ detail: "连接表",
+ type: "keyword",
+ info: "把两张表按条件连接起来查询,需要用 ON 指定连接条件。",
+ boost: 78,
+ apply: "JOIN ",
+ },
+ {
+ label: "LEFT JOIN",
+ detail: "左连接",
+ type: "keyword",
+ info: "以左表为主连接右表,左表的行都保留,右表没匹配的补 NULL。",
+ boost: 76,
+ apply: "LEFT JOIN ",
+ },
+ {
+ label: "ON",
+ detail: "连接条件",
+ type: "keyword",
+ info: "指定两张表的连接条件,如 ON a.id = b.user_id,和 JOIN 搭配。",
+ boost: 74,
+ apply: "ON ",
+ },
+ {
+ label: "INSERT INTO",
+ detail: "插入数据",
+ type: "keyword",
+ info: "向表中插入新行,如 INSERT INTO 表名 (列1, 列2) VALUES (值1, 值2)。",
+ boost: 72,
+ apply: "INSERT INTO ",
+ },
+ {
+ label: "VALUES",
+ detail: "插入的值",
+ type: "keyword",
+ info: "和 INSERT INTO 搭配,写具体要插入的值,顺序要和列名对应。",
+ boost: 70,
+ apply: "VALUES ",
+ },
+ {
+ label: "UPDATE",
+ detail: "更新数据",
+ type: "keyword",
+ info: "修改表中已有的数据,配合 SET 设置新值,别忘了加 WHERE 限定范围。",
+ boost: 68,
+ apply: "UPDATE ",
+ },
+ {
+ label: "SET",
+ detail: "设置新值",
+ type: "keyword",
+ info: "和 UPDATE 搭配,指定要修改的列和新值,如 SET score = 90。",
+ boost: 66,
+ apply: "SET ",
+ },
+ {
+ label: "DELETE FROM",
+ detail: "删除数据",
+ type: "keyword",
+ info: "删除表中的行,一定要配合 WHERE 使用,否则会删掉整张表的数据。",
+ boost: 64,
+ apply: "DELETE FROM ",
+ },
+ {
+ label: "CREATE TABLE",
+ detail: "创建表",
+ type: "keyword",
+ info: "新建一张表,需要定义列名和类型,如 CREATE TABLE users (id INTEGER, name TEXT)。",
+ boost: 62,
+ apply: "CREATE TABLE ",
+ },
+ {
+ label: "DROP TABLE",
+ detail: "删除表",
+ type: "keyword",
+ info: "删除整张表(包括结构和数据),操作不可恢复,要谨慎使用。",
+ boost: 60,
+ apply: "DROP TABLE ",
+ },
+ {
+ label: "ALTER TABLE",
+ detail: "修改表结构",
+ type: "keyword",
+ info: "修改已有表的结构,比如添加列:ALTER TABLE 表名 ADD COLUMN 列名 类型。",
+ boost: 58,
+ apply: "ALTER TABLE ",
+ },
+ {
+ label: "AND",
+ detail: "并且",
+ type: "keyword",
+ info: "连接多个条件,全部成立才算满足,如 WHERE age > 18 AND city = '上海'。",
+ boost: 56,
+ apply: "AND ",
+ },
+ {
+ label: "OR",
+ detail: "或者",
+ type: "keyword",
+ info: "连接多个条件,任意一个成立就算满足。",
+ boost: 54,
+ apply: "OR ",
+ },
+ {
+ label: "NOT",
+ detail: "取反",
+ type: "keyword",
+ info: "对条件取反,如 NOT IN、NOT LIKE、IS NOT NULL。",
+ boost: 52,
+ apply: "NOT ",
+ },
+ {
+ label: "IN",
+ detail: "在列表中",
+ type: "keyword",
+ info: "判断值是否在给定列表中,如 WHERE city IN ('北京', '上海')。",
+ boost: 50,
+ apply: "IN ",
+ },
+ {
+ label: "BETWEEN",
+ detail: "在区间内",
+ type: "keyword",
+ info: "判断值是否在某个范围内(包含两端),如 BETWEEN 60 AND 100。",
+ boost: 48,
+ apply: "BETWEEN ",
+ },
+ {
+ label: "LIKE",
+ detail: "模糊匹配",
+ type: "keyword",
+ info: "模糊查询,% 匹配任意多个字符,_ 匹配单个字符,如 LIKE '张%'。",
+ boost: 46,
+ apply: "LIKE ",
+ },
+ {
+ label: "IS NULL",
+ detail: "是否为空",
+ type: "keyword",
+ info: "判断值是否为 NULL(空值),不能写 = NULL,要用 IS NULL。",
+ boost: 44,
+ },
+ {
+ label: "IS NOT NULL",
+ detail: "是否非空",
+ type: "keyword",
+ info: "判断值不为 NULL,常用于过滤掉缺失数据的行。",
+ boost: 42,
+ },
+ {
+ label: "ASC",
+ detail: "升序",
+ type: "keyword",
+ info: "排序时从小到大排列,是 ORDER BY 的默认方式,可以省略。",
+ boost: 40,
+ },
+ {
+ label: "DESC",
+ detail: "降序",
+ type: "keyword",
+ info: "排序时从大到小排列,如 ORDER BY score DESC 按分数从高到低。",
+ boost: 41,
+ },
+ {
+ label: "UNION",
+ detail: "合并结果",
+ type: "keyword",
+ info: "合并两个查询的结果并去重,两个查询的列数和类型要一致。",
+ boost: 38,
+ apply: "UNION ",
+ },
+ {
+ label: "CASE",
+ detail: "条件表达式",
+ type: "keyword",
+ info: "类似 if/else 的条件判断,语法 CASE WHEN 条件 THEN 值 ELSE 值 END。",
+ boost: 36,
+ apply: "CASE ",
+ },
+ {
+ label: "WHEN",
+ detail: "当条件成立",
+ type: "keyword",
+ info: "和 CASE 搭配,写判断条件,成立时返回 THEN 后面的值。",
+ boost: 34,
+ apply: "WHEN ",
+ },
+ {
+ label: "THEN",
+ detail: "返回值",
+ type: "keyword",
+ info: "和 WHEN 搭配,条件成立时返回的结果。",
+ boost: 32,
+ apply: "THEN ",
+ },
+ {
+ label: "ELSE",
+ detail: "否则",
+ type: "keyword",
+ info: "CASE 中所有 WHEN 都不成立时返回的默认值。",
+ boost: 30,
+ apply: "ELSE ",
+ },
+ {
+ label: "END",
+ detail: "结束 CASE",
+ type: "keyword",
+ info: "标记 CASE 表达式的结束,写 CASE 时不要漏掉。",
+ boost: 28,
+ },
+ {
+ label: "NULL",
+ detail: "空值",
+ type: "keyword",
+ info: "表示没有值(缺失),判断时要用 IS NULL / IS NOT NULL。",
+ boost: 26,
+ },
+ {
+ label: "COUNT",
+ detail: "统计行数",
+ type: "function",
+ info: "统计行数,COUNT(*) 统计所有行,COUNT(列名) 不统计 NULL。",
+ boost: 92,
+ apply: "COUNT()",
+ },
+ {
+ label: "SUM",
+ detail: "求和",
+ type: "function",
+ info: "对某一列求和,只能用于数字列,如 SUM(score)。",
+ boost: 87,
+ apply: "SUM()",
+ },
+ {
+ label: "AVG",
+ detail: "平均值",
+ type: "function",
+ info: "计算某一列的平均值,会自动忽略 NULL,如 AVG(score)。",
+ boost: 85,
+ apply: "AVG()",
+ },
+ {
+ label: "MAX",
+ detail: "最大值",
+ type: "function",
+ info: "求某一列的最大值,如 MAX(score) 找最高分。",
+ boost: 83,
+ apply: "MAX()",
+ },
+ {
+ label: "MIN",
+ detail: "最小值",
+ type: "function",
+ info: "求某一列的最小值,如 MIN(score) 找最低分。",
+ boost: 81,
+ apply: "MIN()",
+ },
+ {
+ label: "LENGTH",
+ detail: "字符串长度",
+ type: "function",
+ info: "返回字符串的字符个数,如 LENGTH(name)。",
+ boost: 79,
+ apply: "LENGTH()",
+ },
+ {
+ label: "UPPER",
+ detail: "转大写",
+ type: "function",
+ info: "把字符串中的字母转成大写,如 UPPER('abc') 得到 'ABC'。",
+ boost: 77,
+ apply: "UPPER()",
+ },
+ {
+ label: "LOWER",
+ detail: "转小写",
+ type: "function",
+ info: "把字符串中的字母转成小写,如 LOWER('ABC') 得到 'abc'。",
+ boost: 75,
+ apply: "LOWER()",
+ },
+ {
+ label: "SUBSTR",
+ detail: "截取子串",
+ type: "function",
+ info: "截取字符串的一部分,语法 SUBSTR(字符串, 起始位置, 长度),位置从 1 开始。",
+ boost: 73,
+ apply: "SUBSTR()",
+ },
+ {
+ label: "REPLACE",
+ detail: "替换字符串",
+ type: "function",
+ info: "把字符串中的内容替换成新内容,语法 REPLACE(字符串, 旧内容, 新内容)。",
+ boost: 71,
+ apply: "REPLACE()",
+ },
+ {
+ label: "ROUND",
+ detail: "四舍五入",
+ type: "function",
+ info: "按指定小数位四舍五入,如 ROUND(3.456, 2) 得到 3.46。",
+ boost: 69,
+ apply: "ROUND()",
+ },
+ {
+ label: "ABS",
+ detail: "绝对值",
+ type: "function",
+ info: "返回数字的绝对值,把负数变成正数,如 ABS(-5) 得到 5。",
+ boost: 67,
+ apply: "ABS()",
+ },
+ {
+ label: "IFNULL",
+ detail: "空值替代",
+ type: "function",
+ info: "如果第一个值是 NULL 就返回第二个值,如 IFNULL(score, 0) 把空分数当 0。",
+ boost: 65,
+ apply: "IFNULL()",
+ },
+ {
+ label: "TRIM",
+ detail: "去首尾空格",
+ type: "function",
+ info: "去掉字符串首尾的空格,常用于清理输入数据。",
+ boost: 63,
+ apply: "TRIM()",
+ },
+]
diff --git a/apps/web/src/shared/layout/admin.vue b/apps/web/src/shared/layout/admin.vue
new file mode 100644
index 0000000..e83042b
--- /dev/null
+++ b/apps/web/src/shared/layout/admin.vue
@@ -0,0 +1,204 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/shared/layout/default.vue b/apps/web/src/shared/layout/default.vue
new file mode 100644
index 0000000..7a01f47
--- /dev/null
+++ b/apps/web/src/shared/layout/default.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/shared/store/achievement.ts b/apps/web/src/shared/store/achievement.ts
new file mode 100644
index 0000000..75086d5
--- /dev/null
+++ b/apps/web/src/shared/store/achievement.ts
@@ -0,0 +1,64 @@
+import {
+ getPendingAchievements,
+ markAchievementsRead,
+} from "oj/achievement/api"
+import type { PendingAchievement } from "utils/types"
+
+/**
+ * 成就解锁弹窗队列。
+ *
+ * 通知走推拉结合,后端的 UserAchievement.notified 是唯一真相来源:
+ * - 拉(主):布局层每次路由切换拉一次 pending,覆盖全部场景,绝不丢
+ * - 推(增强):WebSocket 只在用户当场停留在问题页时把延迟压到几百毫秒
+ *
+ * 之所以不能只靠推:前端 WebSocket 不是常驻连接,只在问题页且有提交监听时
+ * 才建连,纯推会丢消息(尤其是题单奖章,那些页面根本没建连接)。
+ */
+export const useAchievementStore = defineStore("achievement", () => {
+ const queue = ref([])
+ const current = ref(null)
+
+ // 成就和题单奖章的 id 来自两张不同的表,数值会重叠,
+ // 只按 id 去重会让奖章 5 把成就 5 挤掉
+ function keyOf(item: PendingAchievement) {
+ return `${item.kind ?? "achievement"}:${item.id}`
+ }
+
+ function enqueue(items: PendingAchievement[]) {
+ if (!items?.length) return
+ // 去重:WebSocket 推来的和 pending 拉来的可能是同一批
+ const known = new Set([
+ ...queue.value.map(keyOf),
+ ...(current.value ? [keyOf(current.value)] : []),
+ ])
+ queue.value.push(...items.filter((i) => !known.has(keyOf(i))))
+ }
+
+ async function fetchPending() {
+ try {
+ // http 客户端返回 ApiResponse,真实载荷在 .data 里
+ const res = await getPendingAchievements()
+ enqueue(res.data ?? [])
+ } catch {
+ // 拉取失败静默处理,下次路由切换会再拉
+ }
+ }
+
+ function next() {
+ current.value = queue.value.shift() ?? null
+ return current.value
+ }
+
+ async function markRead(item: PendingAchievement) {
+ // 奖章不在 UserAchievement 表里,它的 id 传给标记接口会被当成成就 id,
+ // 把一个恰好同号、还没弹过的成就静默标记为已弹——那个奖杯就再也不会出现
+ if (item.kind === "badge") return
+ try {
+ await markAchievementsRead([item.id])
+ } catch {
+ // 标记失败下次会重复弹一次,可接受
+ }
+ }
+
+ return { queue, current, enqueue, fetchPending, next, markRead }
+})
diff --git a/apps/web/src/shared/store/authModal.ts b/apps/web/src/shared/store/authModal.ts
new file mode 100644
index 0000000..5a8f42b
--- /dev/null
+++ b/apps/web/src/shared/store/authModal.ts
@@ -0,0 +1,155 @@
+import { defineStore } from "pinia"
+
+/**
+ * 认证状态管理 Store
+ * 统一管理登录、注册相关的模态框状态和表单状态
+ */
+export const useAuthModalStore = defineStore("authModal", () => {
+ // ==================== 模态框状态 ====================
+ const loginModalOpen = ref(false)
+ const signupModalOpen = ref(false)
+
+ // ==================== 登录表单状态 ====================
+ const loginForm = reactive({
+ class: "",
+ username: "",
+ password: "",
+ })
+
+ const loginLoading = ref(false)
+ const loginError = ref("")
+
+ // ==================== 注册表单状态 ====================
+ const signupForm = reactive({
+ username: "",
+ email: "",
+ password: "",
+ passwordAgain: "",
+ })
+
+ const signupLoading = ref(false)
+ const signupError = ref("")
+
+ // ==================== 模态框操作 ====================
+ /**
+ * 打开登录模态框
+ */
+ function openLoginModal() {
+ loginModalOpen.value = true
+ }
+
+ /**
+ * 关闭登录模态框
+ */
+ function closeLoginModal() {
+ loginModalOpen.value = false
+ }
+
+ /**
+ * 打开注册模态框
+ */
+ function openSignupModal() {
+ signupModalOpen.value = true
+ }
+
+ /**
+ * 关闭注册模态框
+ */
+ function closeSignupModal() {
+ signupModalOpen.value = false
+ }
+
+ /**
+ * 从登录切换到注册
+ */
+ function switchToSignup() {
+ closeLoginModal()
+ openSignupModal()
+ }
+
+ /**
+ * 从注册切换到登录
+ */
+ function switchToLogin() {
+ closeSignupModal()
+ openLoginModal()
+ }
+
+ // ==================== 登录表单操作 ====================
+ /**
+ * 设置登录加载状态
+ */
+ function setLoginLoading(loading: boolean) {
+ loginLoading.value = loading
+ }
+
+ /**
+ * 设置登录错误信息
+ */
+ function setLoginError(error: string) {
+ loginError.value = error
+ }
+
+ /**
+ * 清空登录错误
+ */
+ function clearLoginError() {
+ loginError.value = ""
+ }
+
+ // ==================== 注册表单操作 ====================
+ /**
+ * 设置注册加载状态
+ */
+ function setSignupLoading(loading: boolean) {
+ signupLoading.value = loading
+ }
+
+ /**
+ * 设置注册错误信息
+ */
+ function setSignupError(error: string) {
+ signupError.value = error
+ }
+
+ /**
+ * 清空注册错误
+ */
+ function clearSignupError() {
+ signupError.value = ""
+ }
+
+ return {
+ // 模态框状态
+ loginModalOpen,
+ signupModalOpen,
+
+ // 登录表单状态
+ loginForm,
+ loginLoading,
+ loginError,
+
+ // 注册表单状态
+ signupForm,
+ signupLoading,
+ signupError,
+
+ // 模态框操作
+ openLoginModal,
+ closeLoginModal,
+ openSignupModal,
+ closeSignupModal,
+ switchToSignup,
+ switchToLogin,
+
+ // 登录表单操作
+ setLoginLoading,
+ setLoginError,
+ clearLoginError,
+
+ // 注册表单操作
+ setSignupLoading,
+ setSignupError,
+ clearSignupError,
+ }
+})
diff --git a/apps/web/src/shared/store/config.ts b/apps/web/src/shared/store/config.ts
new file mode 100644
index 0000000..b317895
--- /dev/null
+++ b/apps/web/src/shared/store/config.ts
@@ -0,0 +1,24 @@
+import { getWebsiteConfig } from "oj/api"
+import type { WebsiteConfig } from "utils/types"
+
+export const useConfigStore = defineStore("config", () => {
+ const config = ref({
+ website_base_url: "",
+ website_name: "",
+ website_name_shortcut: "",
+ website_footer: "",
+ submission_list_show_all: true,
+ allow_register: false,
+ class_list: [],
+ enable_maxkb: true,
+ })
+ async function getConfig() {
+ const res = await getWebsiteConfig()
+ config.value = res.data
+ document.title = res.data.website_name
+ }
+ return {
+ config,
+ getConfig,
+ }
+})
diff --git a/apps/web/src/shared/store/loginSummary.ts b/apps/web/src/shared/store/loginSummary.ts
new file mode 100644
index 0000000..36c1cad
--- /dev/null
+++ b/apps/web/src/shared/store/loginSummary.ts
@@ -0,0 +1,74 @@
+import { getAILoginSummary } from "oj/api"
+
+interface LoginSummary {
+ start: string
+ end: string
+ new_problem_count: number
+ submission_count: number
+ accepted_count: number
+ solved_count: number
+ flowchart_submission_count: number
+}
+
+export const useLoginSummaryStore = defineStore("loginSummary", () => {
+ const show = ref(false)
+ const loading = ref(false)
+ const summary = ref(null)
+ const analysis = ref("")
+ const analysisError = ref("")
+
+ function shouldShowSummary(nextSummary: LoginSummary | null) {
+ if (!nextSummary) {
+ return false
+ }
+ const values = [
+ nextSummary.new_problem_count,
+ nextSummary.submission_count,
+ nextSummary.accepted_count,
+ nextSummary.solved_count,
+ nextSummary.flowchart_submission_count,
+ ]
+ const zeroCount = values.filter((value) => value === 0).length
+ return zeroCount < Math.floor(values.length / 2) + 1
+ }
+
+ async function fetchSummary() {
+ loading.value = true
+ analysis.value = ""
+ analysisError.value = ""
+ try {
+ const res = await getAILoginSummary()
+ summary.value = res.data.summary
+ analysis.value = res.data.analysis || ""
+ analysisError.value = res.data.analysis_error || ""
+ } catch (err) {
+ analysisError.value = "获取登录统计失败,请稍后再试"
+ } finally {
+ loading.value = false
+ }
+ }
+
+ async function open() {
+ await fetchSummary()
+ if (!summary.value && analysisError.value) {
+ show.value = true
+ return
+ }
+ show.value = shouldShowSummary(summary.value)
+ }
+
+ function close() {
+ show.value = false
+ }
+
+ return {
+ show,
+ loading,
+ summary,
+ analysis,
+ analysisError,
+ fetchSummary,
+ open,
+ close,
+ }
+})
diff --git a/apps/web/src/shared/store/myFlowchart.ts b/apps/web/src/shared/store/myFlowchart.ts
new file mode 100644
index 0000000..411bb31
--- /dev/null
+++ b/apps/web/src/shared/store/myFlowchart.ts
@@ -0,0 +1,18 @@
+import { defineStore } from "pinia"
+
+export const useMyFlowchartStore = defineStore("myFlowchart", () => {
+ const showing = ref(false)
+ const mermaidCode = ref("")
+
+ function show(code: string) {
+ mermaidCode.value = code
+ showing.value = true
+ }
+
+ function hide() {
+ showing.value = false
+ mermaidCode.value = ""
+ }
+
+ return { showing, mermaidCode, show, hide }
+})
diff --git a/apps/web/src/shared/store/screenMode.ts b/apps/web/src/shared/store/screenMode.ts
new file mode 100644
index 0000000..29b25c6
--- /dev/null
+++ b/apps/web/src/shared/store/screenMode.ts
@@ -0,0 +1,34 @@
+import { defineStore } from "pinia"
+import { ScreenMode } from "utils/constants"
+
+export const useScreenModeStore = defineStore("screenMode", () => {
+ const { state: screenMode, next: switchScreenMode } = useCycleList(
+ Object.values(ScreenMode),
+ {
+ initialValue: ScreenMode.both,
+ },
+ )
+
+ // 计算属性
+ const isBothMode = computed(() => screenMode.value === ScreenMode.both)
+ const isCodeOnlyMode = computed(() => screenMode.value === ScreenMode.code)
+
+ const shouldShowProblem = computed(
+ () =>
+ screenMode.value === ScreenMode.both ||
+ screenMode.value === ScreenMode.problem,
+ )
+
+ function resetScreenMode() {
+ screenMode.value = ScreenMode.both
+ }
+
+ return {
+ screenMode,
+ isBothMode,
+ isCodeOnlyMode,
+ shouldShowProblem,
+ switchScreenMode,
+ resetScreenMode,
+ }
+})
diff --git a/apps/web/src/shared/store/user.ts b/apps/web/src/shared/store/user.ts
new file mode 100644
index 0000000..f19c39d
--- /dev/null
+++ b/apps/web/src/shared/store/user.ts
@@ -0,0 +1,94 @@
+import { PROBLEM_PERMISSION, STORAGE_KEY, USER_TYPE } from "utils/constants"
+import storage from "utils/storage"
+import type { Profile, User } from "utils/types"
+import { getProfile } from "../api"
+import { useConfigStore } from "./config"
+
+export const useUserStore = defineStore("user", () => {
+ const configStore = useConfigStore()
+
+ const profile = ref(null)
+ const [isFinished] = useToggle(false)
+ const user = computed(() => profile.value?.user ?? null)
+ const isAuthed = computed(() => !!user.value?.email)
+
+ // 演示模式:超管临时把界面伪装成普通学生,方便上课投屏
+ const demoMode = ref(storage.get(STORAGE_KEY.DEMO_MODE) ?? false)
+
+ // 不受伪装影响的真实身份,只用于判断能否切换演示模式。
+ // 若这里用被伪装后的 isSuperAdmin,一进入演示模式入口就消失了,退不出来。
+ const realIsSuperAdmin = computed(
+ () => user.value?.admin_type === USER_TYPE.SUPER_ADMIN,
+ )
+
+ const isAdminRole = computed(
+ () =>
+ !demoMode.value &&
+ (user.value?.admin_type === USER_TYPE.STUDENT_ADMIN ||
+ user.value?.admin_type === USER_TYPE.TEACHER_ADMIN ||
+ user.value?.admin_type === USER_TYPE.SUPER_ADMIN),
+ )
+ const isStudentAdmin = computed(
+ () => !demoMode.value && user.value?.admin_type === USER_TYPE.STUDENT_ADMIN,
+ )
+ const isTeacherAdmin = computed(
+ () => !demoMode.value && user.value?.admin_type === USER_TYPE.TEACHER_ADMIN,
+ )
+ const isTeacherOrAbove = computed(
+ () =>
+ !demoMode.value &&
+ (user.value?.admin_type === USER_TYPE.TEACHER_ADMIN ||
+ user.value?.admin_type === USER_TYPE.SUPER_ADMIN),
+ )
+ const isSuperAdmin = computed(() => !demoMode.value && realIsSuperAdmin.value)
+ const hasProblemPermission = computed(
+ () =>
+ !demoMode.value &&
+ user.value?.problem_permission !== PROBLEM_PERMISSION.NONE,
+ )
+
+ const canToggleDemoMode = computed(() => realIsSuperAdmin.value)
+
+ function toggleDemoMode() {
+ demoMode.value = !demoMode.value
+ storage.set(STORAGE_KEY.DEMO_MODE, demoMode.value)
+ }
+
+ const showSubmissions = computed(() => {
+ let flag = configStore.config.submission_list_show_all
+ if (isAdminRole.value) flag = true
+ return flag
+ })
+
+ async function getMyProfile() {
+ isFinished.value = false
+ const res = await getProfile()
+ profile.value = res.data
+ isFinished.value = true
+ storage.set(STORAGE_KEY.AUTHED, !!user.value?.email)
+ }
+
+ function clearProfile() {
+ profile.value = null
+ demoMode.value = false
+ storage.clear()
+ }
+ return {
+ profile,
+ isFinished,
+ user,
+ isAdminRole,
+ isStudentAdmin,
+ isTeacherAdmin,
+ isTeacherOrAbove,
+ isSuperAdmin,
+ hasProblemPermission,
+ demoMode,
+ canToggleDemoMode,
+ toggleDemoMode,
+ isAuthed,
+ showSubmissions,
+ getMyProfile,
+ clearProfile,
+ }
+})
diff --git a/apps/web/src/shared/themes/createTheme.ts b/apps/web/src/shared/themes/createTheme.ts
new file mode 100644
index 0000000..8190216
--- /dev/null
+++ b/apps/web/src/shared/themes/createTheme.ts
@@ -0,0 +1,110 @@
+import {
+ HighlightStyle,
+ TagStyle,
+ syntaxHighlighting,
+} from "@codemirror/language"
+import { Extension } from "@codemirror/state"
+import { EditorView } from "@codemirror/view"
+
+interface Options {
+ /**
+ * Theme variant. Determines which styles CodeMirror will apply by default.
+ */
+ variant: Variant
+
+ /**
+ * Settings to customize the look of the editor, like background, gutter, selection and others.
+ */
+ settings: Settings
+
+ /**
+ * Syntax highlighting styles.
+ */
+ styles: TagStyle[]
+}
+
+type Variant = "light" | "dark"
+
+interface Settings {
+ /**
+ * Editor background.
+ */
+ background: string
+
+ /**
+ * Default text color.
+ */
+ foreground: string
+
+ /**
+ * Caret color.
+ */
+ caret: string
+
+ /**
+ * Selection background.
+ */
+ selection: string
+
+ /**
+ * Background of highlighted lines.
+ */
+ lineHighlight: string
+
+ /**
+ * Gutter background.
+ */
+ gutterBackground: string
+
+ /**
+ * Text color inside gutter.
+ */
+ gutterForeground: string
+
+ gutterBorderRight: string
+}
+
+export const createTheme = ({
+ variant,
+ settings,
+ styles,
+}: Options): Extension => {
+ const theme = EditorView.theme(
+ {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ "&": {
+ backgroundColor: settings.background,
+ color: settings.foreground,
+ },
+ ".cm-content": {
+ caretColor: settings.caret,
+ },
+ ".cm-cursor, .cm-dropCursor": {
+ borderLeftColor: settings.caret,
+ },
+ "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
+ {
+ backgroundColor: settings.selection,
+ },
+ ".cm-activeLine": {
+ backgroundColor: settings.lineHighlight,
+ },
+ ".cm-gutters": {
+ backgroundColor: settings.gutterBackground,
+ borderRight: settings.gutterBorderRight,
+ color: settings.gutterForeground,
+ },
+ ".cm-activeLineGutter": {
+ backgroundColor: settings.lineHighlight,
+ },
+ },
+ {
+ dark: variant === "dark",
+ },
+ )
+
+ const highlightStyle = HighlightStyle.define(styles)
+ const extension = [theme, syntaxHighlighting(highlightStyle)]
+
+ return extension
+}
diff --git a/apps/web/src/shared/themes/oneDark.ts b/apps/web/src/shared/themes/oneDark.ts
new file mode 100644
index 0000000..2a848c9
--- /dev/null
+++ b/apps/web/src/shared/themes/oneDark.ts
@@ -0,0 +1,149 @@
+import { HighlightStyle, syntaxHighlighting } from "@codemirror/language"
+import { Extension } from "@codemirror/state"
+import { EditorView } from "@codemirror/view"
+import { tags as t } from "@lezer/highlight"
+
+// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors
+
+const chalky = "#e5c07b",
+ coral = "#e06c75",
+ cyan = "#56b6c2",
+ invalid = "#ffffff",
+ ivory = "#abb2bf",
+ stone = "#7d8799", // Brightened compared to original to increase contrast
+ malibu = "#61afef",
+ sage = "#98c379",
+ whiskey = "#d19a66",
+ violet = "#c678dd",
+ darkBackground = "#26262a",
+ highlightBackground = "#2c313a",
+ background = "#101014", // naive-ui
+ tooltipBackground = "#353a42",
+ selection = "#3E4451",
+ cursor = "#528bff"
+
+/// The editor theme styles for One Dark.
+const oneDarkTheme = EditorView.theme(
+ {
+ "&": {
+ color: ivory,
+ backgroundColor: background,
+ },
+
+ ".cm-content": {
+ caretColor: cursor,
+ },
+
+ ".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor },
+ "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
+ { backgroundColor: selection },
+
+ ".cm-panels": { backgroundColor: darkBackground, color: ivory },
+ ".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
+ ".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
+
+ ".cm-searchMatch": {
+ backgroundColor: "#72a1ff59",
+ outline: "1px solid #457dff",
+ },
+ ".cm-searchMatch.cm-searchMatch-selected": {
+ backgroundColor: "#6199ff2f",
+ },
+
+ ".cm-activeLine": { backgroundColor: "#6699ff0b" },
+ ".cm-selectionMatch": { backgroundColor: "#aafe661a" },
+
+ "&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": {
+ backgroundColor: "#bad0f847",
+ },
+
+ ".cm-gutters": {
+ backgroundColor: background,
+ color: stone,
+ border: "none",
+ },
+
+ ".cm-activeLineGutter": {
+ backgroundColor: highlightBackground,
+ },
+
+ ".cm-foldPlaceholder": {
+ backgroundColor: "transparent",
+ border: "none",
+ color: "#ddd",
+ },
+
+ ".cm-tooltip": {
+ border: "none",
+ backgroundColor: tooltipBackground,
+ },
+ ".cm-tooltip .cm-tooltip-arrow:before": {
+ borderTopColor: "transparent",
+ borderBottomColor: "transparent",
+ },
+ ".cm-tooltip .cm-tooltip-arrow:after": {
+ borderTopColor: tooltipBackground,
+ borderBottomColor: tooltipBackground,
+ },
+ ".cm-tooltip-autocomplete": {
+ "& > ul > li[aria-selected]": {
+ backgroundColor: highlightBackground,
+ color: ivory,
+ },
+ },
+ },
+ { dark: true },
+)
+
+/// The highlighting style for code in the One Dark theme.
+const oneDarkHighlightStyle = HighlightStyle.define([
+ { tag: t.keyword, color: violet },
+ {
+ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
+ color: coral,
+ },
+ { tag: [t.function(t.variableName), t.labelName], color: malibu },
+ { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey },
+ { tag: [t.definition(t.name), t.separator], color: ivory },
+ {
+ tag: [
+ t.typeName,
+ t.className,
+ t.number,
+ t.changed,
+ t.annotation,
+ t.modifier,
+ t.self,
+ t.namespace,
+ ],
+ color: chalky,
+ },
+ {
+ tag: [
+ t.operator,
+ t.operatorKeyword,
+ t.url,
+ t.escape,
+ t.regexp,
+ t.link,
+ t.special(t.string),
+ ],
+ color: cyan,
+ },
+ { tag: [t.meta, t.comment], color: stone },
+ { tag: t.strong, fontWeight: "bold" },
+ { tag: t.emphasis, fontStyle: "italic" },
+ { tag: t.strikethrough, textDecoration: "line-through" },
+ { tag: t.link, color: stone, textDecoration: "underline" },
+ { tag: t.heading, fontWeight: "bold", color: coral },
+ { tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey },
+ { tag: [t.processingInstruction, t.string, t.inserted], color: sage },
+ { tag: t.invalid, color: invalid },
+])
+
+/// Extension to enable the One Dark theme (both the editor theme and
+/// the highlight style).
+export const oneDark: Extension = [
+ oneDarkTheme,
+ syntaxHighlighting(oneDarkHighlightStyle),
+]
diff --git a/apps/web/src/shared/themes/smoothy.ts b/apps/web/src/shared/themes/smoothy.ts
new file mode 100644
index 0000000..bb64ee1
--- /dev/null
+++ b/apps/web/src/shared/themes/smoothy.ts
@@ -0,0 +1,100 @@
+import { tags as t } from "@lezer/highlight"
+import { EditorView } from "@codemirror/view"
+import type { Extension } from "@codemirror/state"
+import { createTheme } from "./createTheme"
+
+const tooltipTheme = EditorView.theme({
+ ".cm-tooltip": {
+ border: "1px solid #e0e0e0",
+ backgroundColor: "#f8f8f8",
+ },
+ ".cm-tooltip-autocomplete": {
+ "& > ul > li[aria-selected]": {
+ backgroundColor: "#e8e8e8",
+ color: "#000000",
+ },
+ },
+})
+
+// Author: Kenneth Reitz
+const smoothyBase = createTheme({
+ variant: "light",
+ settings: {
+ background: "#FFFFFF",
+ foreground: "#000000",
+ caret: "#000000",
+ selection: "#FFFD0054",
+ gutterBackground: "#FFFFFF",
+ gutterForeground: "#00000070",
+ gutterBorderRight: "none",
+ lineHighlight: "#00000008",
+ },
+ styles: [
+ {
+ tag: t.comment,
+ color: "#CFCFCF",
+ },
+ {
+ tag: [t.number, t.bool, t.null],
+ color: "#E66C29",
+ },
+ {
+ tag: [
+ t.className,
+ t.definition(t.propertyName),
+ t.function(t.variableName),
+ t.labelName,
+ t.definition(t.typeName),
+ ],
+ color: "#2EB43B",
+ },
+ {
+ tag: t.keyword,
+ color: "#D8B229",
+ },
+ {
+ tag: t.operator,
+ color: "#4EA44E",
+ fontWeight: "bold",
+ },
+ {
+ tag: [t.definitionKeyword, t.modifier],
+ color: "#925A47",
+ },
+ {
+ tag: t.string,
+ color: "#704D3D",
+ },
+ {
+ tag: t.typeName,
+ color: "#2F8996",
+ },
+ {
+ tag: [t.variableName, t.propertyName],
+ color: "#77ACB0",
+ },
+ {
+ tag: t.self,
+ color: "#77ACB0",
+ fontWeight: "bold",
+ },
+ {
+ tag: t.regexp,
+ color: "#E3965E",
+ },
+ {
+ tag: [t.tagName, t.angleBracket],
+ color: "#BAA827",
+ },
+ {
+ tag: t.attributeName,
+ color: "#B06520",
+ },
+ {
+ tag: t.derefOperator,
+ color: "#000",
+ },
+ ],
+})
+
+export const smoothy: Extension = [smoothyBase, tooltipTheme]
diff --git a/apps/web/src/utils/constants.ts b/apps/web/src/utils/constants.ts
new file mode 100644
index 0000000..fe0b84c
--- /dev/null
+++ b/apps/web/src/utils/constants.ts
@@ -0,0 +1,370 @@
+import type { AchievementRarity, SUBMISSION_RESULT, ReactionKey } from "./types"
+
+export enum SubmissionStatus {
+ compile_error = -2,
+ wrong_answer = -1,
+ accepted = 0,
+ time_limit_exceeded = 1 | 2,
+ memory_limit_exceeded = 3,
+ runtime_error = 4,
+ system_error = 5,
+ pending = 6,
+ judging = 7,
+ partial_accepted = 8,
+ submitting = 9,
+ ast_check_failed = 10,
+}
+
+export enum ContestStatus {
+ initial = "2", // 这里不需要传入到后端,只是为了一开始加载数据的时候,做一个初始位
+ not_started = "1",
+ underway = "0",
+ finished = "-1",
+}
+
+export enum ContestType {
+ public = "Public",
+ private = "Password Protected",
+}
+
+export const JUDGE_STATUS: {
+ [key in SUBMISSION_RESULT]: {
+ name: string
+ title: string
+ type: "error" | "success" | "warning" | "info"
+ }
+} = {
+ "-2": {
+ name: "编译失败",
+ title: "编译失败",
+ type: "warning",
+ },
+ "-1": {
+ name: "答案错误",
+ title: "答案错误",
+ type: "error",
+ },
+ "0": {
+ name: "答案正确",
+ title: "答案正确",
+ type: "success",
+ },
+ "1": {
+ name: "运行超时",
+ title: "运行超时",
+ type: "error",
+ },
+ "2": {
+ name: "运行超时",
+ title: "运行超时",
+ type: "error",
+ },
+ "3": {
+ name: "内存超限",
+ title: "内存超限",
+ type: "error",
+ },
+ "4": {
+ name: "运行时错误",
+ title: "运行时错误",
+ type: "warning",
+ },
+ "5": {
+ name: "系统错误",
+ title: "系统错误",
+ type: "error",
+ },
+ "6": {
+ name: "等待评分",
+ title: "等待评分",
+ type: "warning",
+ },
+ "7": {
+ name: "正在评分",
+ title: "正在评分",
+ type: "warning",
+ },
+ "8": {
+ name: "部分正确",
+ title: "部分正确",
+ type: "warning",
+ },
+ "9": {
+ name: "正在提交",
+ title: "正在提交",
+ type: "info",
+ },
+ "10": {
+ name: "语法未通过",
+ title: "答案正确,但语法未通过",
+ type: "success",
+ },
+}
+
+export const CONTEST_STATUS: {
+ [key in ContestStatus]: {
+ name: string
+ type: "error" | "success" | "warning"
+ }
+} = {
+ // 这里不需要传入到后端,只是为了一开始加载数据的时候,做一个初始位
+ "2": {
+ name: "未开始",
+ type: "warning",
+ },
+ "1": {
+ name: "未开始",
+ type: "warning",
+ },
+ "0": {
+ name: "进行中",
+ type: "success",
+ },
+ "-1": {
+ name: "已结束",
+ type: "error",
+ },
+}
+
+export const CONTEST_TYPE = {
+ PUBLIC: "Public",
+ PRIVATE: "Password Protected",
+}
+
+export const USER_TYPE = {
+ REGULAR_USER: "Regular User",
+ STUDENT_ADMIN: "Student Admin",
+ TEACHER_ADMIN: "Teacher Admin",
+ SUPER_ADMIN: "Super Admin",
+}
+
+export const PROBLEM_PERMISSION = {
+ NONE: "None",
+ OWN: "Own",
+ ALL: "All",
+}
+
+export const STORAGE_KEY = {
+ AUTHED: "authed",
+ LANGUAGE: "problemLanguage",
+ LEARN_CURRENT_STEP: "learnStep",
+ ADMIN_PROBLEM: "adminProblem",
+ ADMIN_PROBLEM_TAGS: "adminProblemTags",
+ DEMO_MODE: "demoMode",
+}
+
+export const DIFFICULTY = {
+ Low: "简单",
+ Mid: "中等",
+ High: "困难",
+}
+
+const cSource =
+ "#include\r\n\r\nint main()\r\n{\r\n \r\n return 0;\r\n}"
+const cppSource =
+ "#include\r\n\r\nusing namespace std;\r\n\r\nint main()\r\n{\r\n \r\n return 0;\r\n}"
+const pythonSource = ""
+const javaSource =
+ 'public class Main {\r\n public static void main(String[] args) {\r\n System.out.println("黄岩一职");\r\n }\r\n}'
+
+export const SOURCES = {
+ C: cSource,
+ "C++": cppSource,
+ Java: javaSource,
+ Python3: pythonSource,
+ Python2: "",
+ JavaScript: "",
+ Golang: "",
+ Flowchart: "",
+ SQL: "",
+} as const
+
+export const LANGUAGE_ID = {
+ C: 50,
+ "C++": 54,
+ Java: 62,
+ Python3: 71,
+ Python2: 0,
+ JavaScript: 0,
+ Golang: 0,
+ Flowchart: 0,
+ SQL: 0,
+} as const
+
+export const LANGUAGE_FORMAT_VALUE = {
+ C: "c",
+ "C++": "cpp",
+ Java: "java",
+ Python2: "python",
+ Python3: "python",
+ JavaScript: "javascript",
+ Golang: "go",
+ Flowchart: "flowchart",
+ SQL: "sql",
+} as const
+
+export const LANGUAGE_SHOW_VALUE = {
+ Flowchart: "流程图",
+ C: "C语言",
+ "C++": "C++",
+ Java: "Java",
+ Python2: "Python",
+ Python3: "Python",
+ JavaScript: "JS",
+ Golang: "Go",
+ SQL: "SQL",
+} as const
+
+export const ICON_SET = {
+ Flowchart: "vscode-icons:file-type-drawio",
+ Python2: "devicon:python",
+ Python3: "devicon:python",
+ C: "devicon:c",
+ "C++": "devicon:cplusplus",
+ Java: "devicon:java",
+ JavaScript: "devicon:javascript",
+ Golang: "devicon:go",
+ SQL: "devicon:sqlite",
+} as const
+
+const cTemplate = `//TEMPLATE BEGIN
+#include
+
+int main() {
+ printf("黄岩一职");
+ return 0;
+}
+//TEMPLATE END`
+
+const cppTemplate = `//TEMPLATE BEGIN
+#include
+
+int main() {
+ return 0;
+}
+//TEMPLATE END`
+
+const blankTemplate = `//PREPEND BEGIN
+//PREPEND END
+
+//TEMPLATE BEGIN
+//TEMPLATE END
+
+//APPEND BEGIN
+//APPEND END`
+
+export const CODE_TEMPLATES = {
+ C: cTemplate,
+ "C++": cppTemplate,
+ Python2: blankTemplate,
+ Python3: blankTemplate,
+ Java: blankTemplate,
+ JavaScript: blankTemplate,
+ Golang: blankTemplate,
+ Flowchart: blankTemplate,
+ SQL: blankTemplate,
+} as const
+
+export enum ScreenMode {
+ both = "双栏",
+ code = "自测",
+ problem = "题目",
+}
+
+export enum ChartType {
+ Rank,
+ Activity,
+}
+
+// 成就稀有度
+export const RARITY_LABEL: Record = {
+ bronze: "青铜",
+ silver: "白银",
+ gold: "黄金",
+ platinum: "白金",
+}
+
+// 边框、色块用的原色:饱和度够高,明暗底上都醒目。
+// 青铜往红偏(色相 23),黄金往纯黄推(色相 47),中间隔开两档亮度,
+// 不然两个都落在橙棕区,扫一眼分不出来
+export const RARITY_COLOR: Record = {
+ bronze: "#c2703d",
+ silver: "#9fa6b2",
+ gold: "#f2c012",
+ platinum: "#7dd3fc",
+}
+
+// 文字要另配一套。上面那组是照深色底调的,搬到白底上白金只有 1.7:1、
+// 黄金 2.2:1,12px 的稀有度标签根本看不清。取色见 useRarityColor
+export const RARITY_TEXT_COLOR: Record<
+ "dark" | "light",
+ Record
+> = {
+ dark: {
+ bronze: "#e08d63",
+ silver: "#b6bcc7",
+ gold: "#f2c012",
+ platinum: "#7dd3fc",
+ },
+ light: {
+ bronze: "#a13d1e",
+ silver: "#6b7280",
+ gold: "#8f6f00",
+ platinum: "#0369a1",
+ },
+}
+
+// 时间范围配置
+export const DURATION_OPTIONS = [
+ { label: "本节课内", value: "hours:1" },
+ { label: "两节课内", value: "hours:2" },
+ { label: "一天内", value: "days:1" },
+ { label: "一周内", value: "weeks:1" },
+ { label: "一个月内", value: "months:1" },
+ { label: "两个月内", value: "months:2" },
+ { label: "半年内", value: "months:6" },
+ { label: "一年内", value: "years:1" },
+] as const
+
+// 班级号的位数范围。学生用户名形如 ks<班级号><姓名>,班级号还要跟
+// 网站配置里的班级列表对得上。
+// 后端 OnlineJudge/utils/shortcuts.py 的 CLASS_NAME_MIN/MAX_DIGITS
+// 是同一条规则的另一份,改这里必须同时改那边。
+export const CLASS_NAME_MIN_DIGITS = 3
+export const CLASS_NAME_MAX_DIGITS = 4
+
+/** 合法班级号:3~4 位纯数字 */
+export const CLASS_NAME_RE = new RegExp(
+ `^\\d{${CLASS_NAME_MIN_DIGITS},${CLASS_NAME_MAX_DIGITS}}$`,
+)
+
+/** 用户名开头的 ks<班级号>,用于从用户名里认出班级 */
+export const USERNAME_CLASS_RE = new RegExp(
+ `^ks\\d{${CLASS_NAME_MIN_DIGITS},${CLASS_NAME_MAX_DIGITS}}`,
+)
+
+/** 班级号作为数字时的上下界,给 n-input-number 用 */
+export const CLASS_NAME_MIN_VALUE = 10 ** (CLASS_NAME_MIN_DIGITS - 1)
+export const CLASS_NAME_MAX_VALUE = 10 ** CLASS_NAME_MAX_DIGITS - 1
+
+export const REACTIONS: {
+ key: ReactionKey
+ label: string
+ icon: string
+}[] = [
+ {
+ key: "too_easy",
+ label: "太简单",
+ icon: "fluent-emoji:smiling-face-with-sunglasses",
+ },
+ { key: "too_hard", label: "太难了", icon: "fluent-emoji:exploding-head" },
+ {
+ key: "confusing",
+ label: "没看懂",
+ icon: "fluent-emoji:face-with-spiral-eyes",
+ },
+ { key: "buggy", label: "题目有错", icon: "fluent-emoji:bug" },
+ { key: "learned", label: "学到了", icon: "fluent-emoji:light-bulb" },
+ { key: "interesting", label: "有意思", icon: "fluent-emoji:star-struck" },
+ { key: "want_explain", label: "想听讲解", icon: "fluent-emoji:books" },
+]
diff --git a/apps/web/src/utils/download.ts b/apps/web/src/utils/download.ts
new file mode 100644
index 0000000..327e17a
--- /dev/null
+++ b/apps/web/src/utils/download.ts
@@ -0,0 +1,23 @@
+import axios from "axios"
+
+const http = axios.create({
+ baseURL: "/api/admin",
+ responseType: "blob",
+ xsrfHeaderName: "X-CSRFToken",
+ xsrfCookieName: "csrftoken",
+})
+
+async function download(url: string) {
+ const res = await http.get(url)
+ const headers = res.headers
+ const link = document.createElement("a")
+ link.href = window.URL.createObjectURL(
+ new window.Blob([res.data], { type: headers["content-type"] }),
+ )
+ link.download = (headers["content-disposition"] || "").split("filename=")[1]
+ document.body.appendChild(link)
+ link.click()
+ link.remove()
+}
+
+export default download
diff --git a/apps/web/src/utils/functions.ts b/apps/web/src/utils/functions.ts
new file mode 100644
index 0000000..3ec9c7d
--- /dev/null
+++ b/apps/web/src/utils/functions.ts
@@ -0,0 +1,600 @@
+import { getTime, intervalToDuration, parseISO, type Duration } from "date-fns"
+import { User } from "./types"
+import { USER_TYPE } from "./constants"
+import {
+ strFromU8,
+ strToU8,
+ unzlibSync,
+ zipSync,
+ zlibSync,
+ type Zippable,
+} from "fflate"
+import copyTextFallback from "copy-text-to-clipboard"
+import { customAlphabet } from "nanoid"
+
+function calculateACRate(acCount: number, totalCount: number): string {
+ if (totalCount === 0) return "0.00"
+ if (acCount >= totalCount) return "100.00"
+ return ((acCount / totalCount) * 100).toFixed(2)
+}
+
+export function getACRate(acCount: number, totalCount: number): string {
+ return `${calculateACRate(acCount, totalCount)}%`
+}
+
+export function getACRateNumber(acCount: number, totalCount: number): number {
+ return parseFloat(calculateACRate(acCount, totalCount))
+}
+
+export function filterEmptyValue>(
+ object: T,
+): Partial {
+ return Object.entries(object).reduce((query, [key, value]) => {
+ if (value != null && value !== "" && value !== undefined) {
+ query[key as keyof T] = value
+ }
+ return query
+ }, {} as Partial)
+}
+
+export function getTagColor(
+ tag: "Low" | "Mid" | "High" | "简单" | "中等" | "困难",
+) {
+ return <"success" | "info" | "error">{
+ Low: "success",
+ Mid: "info",
+ High: "error",
+ 简单: "success",
+ 中等: "info",
+ 困难: "error",
+ }[tag]
+}
+
+// 2023-04-03T02:43:28.673156Z
+export function parseTime(utc: Date | string, format = "YYYY年M月D日") {
+ const time = useDateFormat(utc, format, { locales: "zh-CN" })
+ return time.value
+}
+
+function getDurationObject(start: Date | string, end: Date | string) {
+ return intervalToDuration({
+ start: getTime(parseISO(start.toString())),
+ end: getTime(parseISO(end.toString())),
+ })
+}
+
+function formatDurationUnits(
+ duration: Duration,
+ units: Array<{ key: keyof Duration; suffix: string }>,
+): string {
+ return units
+ .filter(({ key }) => duration[key])
+ .map(({ key, suffix }) => duration[key] + suffix)
+ .join("")
+}
+
+export function duration(
+ start: Date | string,
+ end: Date | string,
+ showSeconds = false,
+): string {
+ const durationObj = getDurationObject(start, end)
+ const units = [
+ { key: "years" as const, suffix: "年" },
+ { key: "months" as const, suffix: "月" },
+ { key: "days" as const, suffix: "天" },
+ { key: "hours" as const, suffix: "小时" },
+ { key: "minutes" as const, suffix: "分钟" },
+ ...(showSeconds ? [{ key: "seconds" as const, suffix: "秒" }] : []),
+ ]
+ return formatDurationUnits(durationObj, units)
+}
+
+export function durationToDays(
+ start: Date | string,
+ end: Date | string,
+): string {
+ const durationObj = getDurationObject(start, end)
+ const units = [
+ { key: "years" as const, suffix: "年" },
+ { key: "months" as const, suffix: "月" },
+ { key: "days" as const, suffix: "天" },
+ ]
+ const result = formatDurationUnits(durationObj, units)
+ return result || "一天以内"
+}
+
+export function secondsToDuration(seconds: number): string {
+ const duration = intervalToDuration({
+ start: 0,
+ end: seconds * 1000,
+ })
+ const hours = (duration.days ?? 0) * 24 + (duration.hours ?? 0)
+ const pad = (n: number) => String(n).padStart(2, "0")
+ return [hours, pad(duration.minutes ?? 0), pad(duration.seconds ?? 0)].join(
+ ":",
+ )
+}
+
+export function submissionMemoryFormat(memory: number | string | undefined) {
+ if (memory === undefined) return "--"
+ // 1048576 = 1024 * 1024
+ let t = parseInt(memory + "") / 1048576
+ return String(t.toFixed(0)) + "MB"
+}
+
+export function submissionTimeFormat(time: number | string | undefined) {
+ if (time === undefined) return "--"
+ return time + "ms"
+}
+
+export function debounce any>(
+ fn: T,
+ delay = 100,
+): (...args: Parameters) => void {
+ let timeoutId: ReturnType
+ return (...args: Parameters) => {
+ clearTimeout(timeoutId)
+ timeoutId = setTimeout(() => fn(...args), delay)
+ }
+}
+
+export function getUserRole(role: User["admin_type"]): {
+ type: "default" | "info" | "warning" | "error"
+ label: "普通" | "学生管理员" | "教师管理员" | "超管"
+} {
+ const roleMap = {
+ [USER_TYPE.REGULAR_USER]: {
+ type: "default" as const,
+ label: "普通" as const,
+ },
+ [USER_TYPE.STUDENT_ADMIN]: {
+ type: "info" as const,
+ label: "学生管理员" as const,
+ },
+ [USER_TYPE.TEACHER_ADMIN]: {
+ type: "warning" as const,
+ label: "教师管理员" as const,
+ },
+ [USER_TYPE.SUPER_ADMIN]: {
+ type: "error" as const,
+ label: "超管" as const,
+ },
+ }
+
+ return roleMap[role] || roleMap[USER_TYPE.REGULAR_USER]
+}
+
+export function unique(arr: T[]): T[] {
+ return [...new Set(arr)]
+}
+
+export function encode(string?: string): string {
+ try {
+ return btoa(String.fromCharCode(...new TextEncoder().encode(string ?? "")))
+ } catch (error) {
+ console.error("编码失败:", error)
+ return ""
+ }
+}
+
+export function decode(bytes?: string): string {
+ try {
+ if (!bytes) return ""
+ const latin = atob(bytes)
+ return new TextDecoder("utf-8").decode(
+ Uint8Array.from({ length: latin.length }, (_, index) =>
+ latin.charCodeAt(index),
+ ),
+ )
+ } catch (error) {
+ console.error("解码失败:", error)
+ return ""
+ }
+}
+
+export function getCSRFToken(): string {
+ if (typeof document === "undefined") {
+ return ""
+ }
+ const match = document.cookie.match(/(?:^|;\s*)csrftoken=([^;]+)/)
+ return match ? decodeURIComponent(match[1]) : ""
+}
+
+export function utoa(data: string): string {
+ const buffer = strToU8(data)
+ const zipped = zlibSync(buffer, { level: 9 })
+ const binary = strFromU8(zipped, true)
+ return btoa(binary)
+}
+
+export function atou(base64: string): string {
+ const binary = atob(base64)
+ const buffer = strToU8(binary, true)
+ const unzipped = unzlibSync(buffer)
+ return strFromU8(unzipped)
+}
+
+/**
+ * 把若干文本文件打包成 zip Blob
+ * @param files 文件名和文本内容
+ * @param mtime 归档内的修改时间,默认当前时间
+ */
+export function createZipBlob(
+ files: { name: string; content: string }[],
+ mtime: Date = new Date(),
+): Blob {
+ const entries: Zippable = {}
+ for (const f of files) {
+ entries[f.name] = strToU8(f.content)
+ }
+ return new Blob([zipSync(entries, { mtime })], { type: "application/zip" })
+}
+
+/**
+ * 复制文本到剪贴板
+ * 优先使用 Clipboard API(支持在 modal 中使用),失败时回退到 copy-text-to-clipboard
+ * @param text 要复制的文本
+ * @returns Promise 复制是否成功
+ */
+export async function copyToClipboard(text: string): Promise {
+ // 优先使用现代 Clipboard API
+ if (navigator.clipboard && window.isSecureContext) {
+ try {
+ await navigator.clipboard.writeText(text)
+ return true
+ } catch (error) {
+ console.warn("Clipboard API 复制失败,尝试使用回退方法:", error)
+ }
+ }
+
+ // 回退到 copy-text-to-clipboard
+ try {
+ const success = copyTextFallback(text)
+ return success
+ } catch (error) {
+ console.error("复制失败:", error)
+ return false
+ }
+}
+
+export function getRandomId() {
+ const nanoid = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz")
+ return nanoid()
+}
+
+/**
+ * 恶搞效果函数 - 随机触发不同的页面恶搞效果
+ */
+export function trickOrTreat() {
+ const effects = [
+ // 效果1: 中文乱码
+ () => {
+ document.body.innerHTML = document.body.innerHTML.replace(
+ /[\u4e00-\u9fa5]/g,
+ function (c) {
+ return String.fromCharCode(c.charCodeAt(0) ^ 0xa5) // 将中文字符转为乱码
+ },
+ )
+ },
+ // 效果2: 页面一直缩放
+ () => {
+ const style = document.createElement("style")
+ style.id = "trick-scale-style"
+ style.textContent = `
+ body {
+ animation: trickScale 0.5s ease-in-out infinite alternate;
+ }
+ @keyframes trickScale {
+ from { transform: scale(0.8); }
+ to { transform: scale(1.2); }
+ }
+ `
+ document.head.appendChild(style)
+ },
+ // 效果3: 页面左右颠倒
+ () => {
+ document.body.style.transform = "scaleX(-1)"
+ },
+ // 效果4: 去掉鼠标
+ () => {
+ const style = document.createElement("style")
+ style.id = "trick-cursor-none-style"
+ style.textContent = `
+ * {
+ cursor: none !important;
+ }
+ `
+ document.head.appendChild(style)
+ },
+ // 效果5: 页面一直旋转
+ () => {
+ const style = document.createElement("style")
+ style.id = "trick-rotate-style"
+ style.textContent = `
+ body {
+ animation: trickRotate 2s linear infinite;
+ }
+ @keyframes trickRotate {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+ }
+ `
+ document.head.appendChild(style)
+ },
+ // 效果6: 页面上下颠倒
+ () => {
+ document.body.style.transform = "scaleY(-1)"
+ },
+ // 效果7: 页面颜色反转(反色)
+ () => {
+ const style = document.createElement("style")
+ style.id = "trick-invert-style"
+ style.textContent = `
+ body {
+ filter: invert(1) !important;
+ }
+ `
+ document.head.appendChild(style)
+ },
+ // 效果8: 页面抖动/震动
+ () => {
+ const style = document.createElement("style")
+ style.id = "trick-shake-style"
+ style.textContent = `
+ body {
+ animation: trickShake 0.1s ease-in-out infinite;
+ }
+ @keyframes trickShake {
+ 0%, 100% { transform: translate(0, 0); }
+ 25% { transform: translate(-5px, -5px); }
+ 50% { transform: translate(5px, 5px); }
+ 75% { transform: translate(-5px, 5px); }
+ }
+ `
+ document.head.appendChild(style)
+ },
+ // 效果9: 页面模糊
+ () => {
+ const style = document.createElement("style")
+ style.id = "trick-blur-style"
+ style.textContent = `
+ body {
+ filter: blur(5px) !important;
+ }
+ `
+ document.head.appendChild(style)
+ },
+ // 效果10: 点击哪里,哪里的DOM就飞掉
+ () => {
+ // 添加CSS动画样式
+ const style = document.createElement("style")
+ style.id = "trick-flyaway-style"
+ style.textContent = `
+ .trick-flyaway {
+ animation: trickFlyAway 0.5s ease-out forwards !important;
+ pointer-events: none !important;
+ }
+ @keyframes trickFlyAway {
+ 0% {
+ transform: translate(0, 0) rotate(0deg);
+ opacity: 1;
+ }
+ 100% {
+ transform: translate(var(--fly-x, 500px), var(--fly-y, -500px)) rotate(720deg);
+ opacity: 0;
+ }
+ }
+ `
+ document.head.appendChild(style)
+
+ // 添加全局点击事件监听器
+ const clickHandler = (e: MouseEvent) => {
+ const target = e.target as HTMLElement
+ // 跳过body和html元素,以及已经飞走的元素
+ if (
+ target === document.body ||
+ target === document.documentElement ||
+ target.classList.contains("trick-flyaway")
+ ) {
+ return
+ }
+
+ // 获取点击位置相对于视口的位置
+ const rect = target.getBoundingClientRect()
+ const clickX = e.clientX - rect.left - rect.width / 2
+ const clickY = e.clientY - rect.top - rect.height / 2
+
+ // 计算飞出方向(随机方向)
+ const angle = Math.random() * Math.PI * 2
+ const distance = 1000 + Math.random() * 500
+ const flyX = Math.cos(angle) * distance
+ const flyY = Math.sin(angle) * distance
+
+ // 设置CSS变量
+ target.style.setProperty("--fly-x", `${flyX}px`)
+ target.style.setProperty("--fly-y", `${flyY}px`)
+
+ // 添加飞走动画类
+ target.classList.add("trick-flyaway")
+
+ // 动画结束后移除元素或隐藏
+ setTimeout(() => {
+ target.style.display = "none"
+ }, 500)
+ }
+
+ document.addEventListener("click", clickHandler, true)
+ },
+ // 效果11: 页面元素随机位置
+ () => {
+ const style = document.createElement("style")
+ style.id = "trick-random-position-style"
+ style.textContent = `
+ * {
+ position: relative !important;
+ }
+ `
+ document.head.appendChild(style)
+
+ // 随机移动所有元素
+ const allElements = document.querySelectorAll("*")
+ allElements.forEach((el) => {
+ if (el === document.body || el === document.documentElement) return
+ const element = el as HTMLElement
+ const randomX = (Math.random() - 0.5) * 200
+ const randomY = (Math.random() - 0.5) * 200
+ element.style.transform = `translate(${randomX}px, ${randomY}px)`
+ })
+ },
+ // 效果12: 页面变成3D倾斜效果
+ () => {
+ const style = document.createElement("style")
+ style.id = "trick-3d-tilt-style"
+ style.textContent = `
+ body {
+ perspective: 1000px;
+ transform-style: preserve-3d;
+ animation: trick3DTilt 3s ease-in-out infinite alternate;
+ }
+ @keyframes trick3DTilt {
+ 0% {
+ transform: perspective(1000px) rotateX(0deg) rotateY(0deg);
+ }
+ 25% {
+ transform: perspective(1000px) rotateX(15deg) rotateY(-15deg);
+ }
+ 50% {
+ transform: perspective(1000px) rotateX(-15deg) rotateY(15deg);
+ }
+ 75% {
+ transform: perspective(1000px) rotateX(15deg) rotateY(15deg);
+ }
+ 100% {
+ transform: perspective(1000px) rotateX(-15deg) rotateY(-15deg);
+ }
+ }
+ `
+ document.head.appendChild(style)
+ },
+ // 效果13: 页面所有链接失效
+ () => {
+ const clickHandler = (e: MouseEvent) => {
+ const target = e.target as HTMLElement
+ // 检查是否是链接或按钮
+ if (
+ target.tagName === "A" ||
+ target.closest("a") ||
+ (target.tagName === "BUTTON" && !target.closest("n-button"))
+ ) {
+ e.preventDefault()
+ e.stopPropagation()
+ e.stopImmediatePropagation()
+ return false
+ }
+ }
+ document.addEventListener("click", clickHandler, true)
+ },
+ // 效果14: 页面变成波浪效果
+ () => {
+ const style = document.createElement("style")
+ style.id = "trick-wave-style"
+ style.textContent = `
+ body {
+ animation: trickWave 2s ease-in-out infinite;
+ }
+ @keyframes trickWave {
+ 0%, 100% {
+ transform: translateY(0) scaleY(1);
+ }
+ 25% {
+ transform: translateY(-10px) scaleY(1.02);
+ }
+ 50% {
+ transform: translateY(0) scaleY(1);
+ }
+ 75% {
+ transform: translateY(10px) scaleY(0.98);
+ }
+ }
+ `
+ document.head.appendChild(style)
+ },
+ // 效果15: 页面变成故障效果(Glitch)
+ () => {
+ const style = document.createElement("style")
+ style.id = "trick-glitch-style"
+ style.textContent = `
+ body {
+ animation: trickGlitch 0.3s infinite;
+ }
+ @keyframes trickGlitch {
+ 0% {
+ transform: translate(0);
+ filter: hue-rotate(0deg);
+ }
+ 20% {
+ transform: translate(-2px, 2px);
+ filter: hue-rotate(90deg);
+ }
+ 40% {
+ transform: translate(-2px, -2px);
+ filter: hue-rotate(180deg);
+ }
+ 60% {
+ transform: translate(2px, 2px);
+ filter: hue-rotate(270deg);
+ }
+ 80% {
+ transform: translate(2px, -2px);
+ filter: hue-rotate(360deg);
+ }
+ 100% {
+ transform: translate(0);
+ filter: hue-rotate(0deg);
+ }
+ }
+ body::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background:
+ repeating-linear-gradient(
+ 0deg,
+ transparent,
+ transparent 2px,
+ rgba(255, 0, 0, 0.03) 2px,
+ rgba(255, 0, 0, 0.03) 4px
+ ),
+ repeating-linear-gradient(
+ 90deg,
+ transparent,
+ transparent 2px,
+ rgba(0, 255, 255, 0.03) 2px,
+ rgba(0, 255, 255, 0.03) 4px
+ );
+ pointer-events: none;
+ z-index: 9999;
+ mix-blend-mode: difference;
+ }
+ `
+ document.head.appendChild(style)
+ },
+ ]
+
+ // 随机选择一种效果
+ const randomEffect = effects[Math.floor(Math.random() * effects.length)]
+ randomEffect()
+}
+
+// function getChromeVersion() {
+// var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)
+// return raw ? parseInt(raw[2], 10) : 0
+// }
+
+// export const isLowVersion = getChromeVersion() < 80
+
+// export const protocol = isLowVersion ? "http" : "https"
diff --git a/apps/web/src/utils/http.ts b/apps/web/src/utils/http.ts
new file mode 100644
index 0000000..40d90be
--- /dev/null
+++ b/apps/web/src/utils/http.ts
@@ -0,0 +1,79 @@
+import axios, { type AxiosRequestConfig } from "axios"
+import { createDiscreteApi } from "naive-ui"
+import { useAuthModalStore } from "shared/store/authModal"
+import storage from "./storage"
+import { STORAGE_KEY } from "./constants"
+
+const { message } = createDiscreteApi(["message"])
+
+// 后端统一返回 { error, data } 信封;拦截器剥掉 axios 外层后,
+// 调用方拿到的就是这个信封,data 才是真正的业务数据。
+export interface ApiResponse {
+ error: string | null
+ data: T
+}
+
+// 让 http.get() 的类型真实反映"解包后返回信封"这件事,
+// 调用方 res.data 直接拿到带类型的 T,不再依赖 axios 的 AxiosResponse 巧合对齐。
+interface Http {
+ get(
+ url: string,
+ config?: AxiosRequestConfig,
+ ): Promise>
+ delete(
+ url: string,
+ config?: AxiosRequestConfig,
+ ): Promise>
+ post(
+ url: string,
+ data?: unknown,
+ config?: AxiosRequestConfig,
+ ): Promise>
+ put(
+ url: string,
+ data?: unknown,
+ config?: AxiosRequestConfig,
+ ): Promise>
+}
+
+const instance = axios.create({
+ baseURL: "/api",
+ xsrfHeaderName: "X-CSRFToken",
+ xsrfCookieName: "csrftoken",
+})
+
+// 统一剥掉空字符串 / null / undefined 的 query 参数,
+// 各 api 函数不必再手写过滤逻辑(保留 0、false)。
+instance.interceptors.request.use((config) => {
+ if (config.params) {
+ config.params = Object.fromEntries(
+ Object.entries(config.params).filter(
+ ([, v]) => v !== "" && v !== null && v !== undefined,
+ ),
+ )
+ }
+ return config
+})
+
+instance.interceptors.response.use(
+ (res) => {
+ if (res.data.error) {
+ if (res.data.error === "login-required") {
+ storage.remove(STORAGE_KEY.AUTHED)
+ useAuthModalStore().openLoginModal()
+ } else if (res.data.error === "permission-denied") {
+ message.error(res.data.data || "权限不足")
+ }
+ return Promise.reject(res.data)
+ } else {
+ return Promise.resolve(res.data)
+ }
+ },
+ (err) => {
+ return Promise.reject(err)
+ },
+)
+
+const http = instance as unknown as Http
+
+export default http
diff --git a/apps/web/src/utils/judge.ts b/apps/web/src/utils/judge.ts
new file mode 100644
index 0000000..6015d21
--- /dev/null
+++ b/apps/web/src/utils/judge.ts
@@ -0,0 +1,37 @@
+import axios from "axios"
+import { decode, encode } from "./functions"
+import { Code } from "./types"
+
+const http = axios.create({ baseURL: import.meta.env.PUBLIC_JUDGE0_URL })
+
+export async function createTestSubmission(code: Code, input: string) {
+ const encodedCode = encode(code.value)
+ const id = {
+ C: 50,
+ "C++": 54,
+ Java: 62,
+ Golang: 60,
+ JavaScript: 63,
+ Python2: 70,
+ Python3: 71,
+ }[code.language]
+ let compilerOptions = ""
+ if (id === 50) compilerOptions = "-lm" // 解决 GCC 的链接问题
+ const payload = {
+ source_code: encodedCode,
+ language_id: id,
+ stdin: encode(input),
+ redirect_stderr_to_stdout: true,
+ compiler_options: compilerOptions,
+ }
+ const response = await http.post("/submissions", payload, {
+ params: { base64_encoded: true, wait: true },
+ })
+ const data = response.data
+ return {
+ status: data.status && data.status.id,
+ output: [decode(data.compile_output), decode(data.stdout)]
+ .join("\n")
+ .trim(),
+ }
+}
diff --git a/apps/web/src/utils/permissions.ts b/apps/web/src/utils/permissions.ts
new file mode 100644
index 0000000..861529d
--- /dev/null
+++ b/apps/web/src/utils/permissions.ts
@@ -0,0 +1,108 @@
+import { useUserStore } from "shared/store/user"
+
+export function usePermissions() {
+ const userStore = useUserStore()
+
+ return {
+ isAuthenticated: computed(() => userStore.isAuthed),
+ isAdminRole: computed(() => userStore.isAdminRole),
+ isTeacherOrAbove: computed(() => userStore.isTeacherOrAbove),
+ isSuperAdmin: computed(() => userStore.isSuperAdmin),
+ hasProblemPermission: computed(() => userStore.hasProblemPermission),
+
+ canManageUsers: computed(() => userStore.isSuperAdmin),
+ canManageAnnouncements: computed(() => userStore.isSuperAdmin),
+ canManageTutorials: computed(() => userStore.isSuperAdmin),
+ canManageSystemConfig: computed(() => userStore.isSuperAdmin),
+ canSendMessages: computed(() => userStore.isSuperAdmin),
+
+ canManageProblems: computed(() => userStore.hasProblemPermission),
+ canManageContests: computed(() => userStore.isTeacherOrAbove),
+ canManageProblemsets: computed(() => userStore.isTeacherOrAbove),
+ canViewClassroomData: computed(() => userStore.isTeacherOrAbove),
+
+ canManageAllProblems: computed(
+ () =>
+ userStore.user?.problem_permission === "All" || userStore.isSuperAdmin,
+ ),
+ canManageOwnProblems: computed(
+ () =>
+ userStore.user?.problem_permission === "Own" && !userStore.isSuperAdmin,
+ ),
+
+ getUserPermissionLevel: computed(() => {
+ if (userStore.isSuperAdmin) return "超级管理员"
+ if (userStore.isTeacherAdmin) return "教师管理员"
+ if (userStore.isStudentAdmin) return "学生管理员"
+ return "普通用户"
+ }),
+
+ getProblemPermissionLevel: computed(() => {
+ if (!userStore.user) return "无权限"
+ switch (userStore.user.problem_permission) {
+ case "All":
+ return "管理所有题目"
+ case "Own":
+ return "管理自己的题目"
+ case "None":
+ return "无题目权限"
+ default:
+ return "无权限"
+ }
+ }),
+ }
+}
+
+export function checkRoutePermission(routeName: string): boolean {
+ const userStore = useUserStore()
+
+ const superAdminRoutes = [
+ "admin home",
+ "admin config",
+ "admin user list",
+ "admin user generate",
+ "admin announcement list",
+ "admin announcement create",
+ "admin announcement edit",
+ "admin message list",
+ "admin tutorial list",
+ "admin tutorial create",
+ "admin tutorial edit",
+ ]
+
+ const teacherAdminRoutes = [
+ "admin contest list",
+ "admin contest create",
+ "admin contest edit",
+ "admin contest problem list",
+ "admin contest problem create",
+ "admin contest problem edit",
+ "admin contest helper",
+ "admin problemset list",
+ "admin problemset create",
+ "admin problemset edit",
+ "admin problemset detail",
+ "admin stuck problems",
+ "admin top ac trend",
+ ]
+
+ const problemPermissionRoutes = [
+ "admin problem list",
+ "admin problem create",
+ "admin problem edit",
+ ]
+
+ if (superAdminRoutes.includes(routeName)) {
+ return userStore.isSuperAdmin
+ }
+
+ if (teacherAdminRoutes.includes(routeName)) {
+ return userStore.isTeacherOrAbove
+ }
+
+ if (problemPermissionRoutes.includes(routeName)) {
+ return userStore.hasProblemPermission
+ }
+
+ return true
+}
diff --git a/apps/web/src/utils/renders.ts b/apps/web/src/utils/renders.ts
new file mode 100644
index 0000000..02f4be1
--- /dev/null
+++ b/apps/web/src/utils/renders.ts
@@ -0,0 +1,9 @@
+import { Icon } from "@iconify/vue"
+import { NFlex } from "naive-ui"
+
+export function renderTableTitle(title: string, icon: string): any {
+ return h(NFlex, { align: "center", size: 4 }, () => [
+ h(Icon, { icon: icon, width: 20, height: 20 }),
+ title,
+ ])
+}
diff --git a/apps/web/src/utils/storage.ts b/apps/web/src/utils/storage.ts
new file mode 100644
index 0000000..feb1ca5
--- /dev/null
+++ b/apps/web/src/utils/storage.ts
@@ -0,0 +1,21 @@
+const localStorage = window.localStorage
+
+export default {
+ set(key: string, value: any) {
+ localStorage.setItem(key, JSON.stringify(value))
+ },
+ get(key: string) {
+ const content = localStorage.getItem(key)
+ if (content) {
+ return JSON.parse(content)
+ } else {
+ return null
+ }
+ },
+ remove(key: string) {
+ localStorage.removeItem(key)
+ },
+ clear() {
+ localStorage.clear()
+ },
+}
diff --git a/apps/web/src/utils/stream.ts b/apps/web/src/utils/stream.ts
new file mode 100644
index 0000000..fc6b6ef
--- /dev/null
+++ b/apps/web/src/utils/stream.ts
@@ -0,0 +1,94 @@
+export interface JSONEventStreamHandlers {
+ onMessage: (data: T, event?: string) => void
+ onEvent?: (event: string) => void
+ signal?: AbortSignal | null
+}
+
+export async function consumeJSONEventStream(
+ response: Response,
+ handlers: JSONEventStreamHandlers,
+) {
+ if (!response.body) {
+ throw new Error("当前环境不支持可读流")
+ }
+
+ const reader = response.body.getReader()
+ const decoder = new TextDecoder("utf-8")
+ let buffer = ""
+
+ const { onMessage, onEvent, signal } = handlers
+
+ const handleEvent = (raw: string) => {
+ const lines = raw.split("\n")
+ let eventName: string | undefined
+ const dataLines: string[] = []
+
+ for (const line of lines) {
+ const trimmed = line.trim()
+ if (!trimmed) continue
+ if (trimmed.startsWith("event:")) {
+ eventName = trimmed.slice(6).trim()
+ } else if (trimmed.startsWith("data:")) {
+ dataLines.push(trimmed.slice(5).trim())
+ }
+ }
+
+ if (dataLines.length === 0) {
+ if (eventName && onEvent) {
+ onEvent(eventName)
+ }
+ return
+ }
+
+ const payloadStr = dataLines.join("\n")
+
+ let parsed: T
+ try {
+ parsed = JSON.parse(payloadStr)
+ } catch (error) {
+ throw new Error(`无法解析服务端事件数据: ${payloadStr}`)
+ }
+
+ onMessage(parsed, eventName)
+ }
+
+ const processBuffer = (flush = false) => {
+ let idx = buffer.indexOf("\n\n")
+ while (idx !== -1) {
+ const rawEvent = buffer.slice(0, idx)
+ buffer = buffer.slice(idx + 2)
+ if (rawEvent.trim()) {
+ handleEvent(rawEvent)
+ }
+ idx = buffer.indexOf("\n\n")
+ }
+
+ if (flush && buffer.trim()) {
+ handleEvent(buffer.trim())
+ buffer = ""
+ }
+ }
+
+ try {
+ while (true) {
+ if (signal?.aborted) {
+ await reader.cancel()
+ break
+ }
+
+ const { value, done } = await reader.read()
+ if (value) {
+ buffer += decoder.decode(value, { stream: true })
+ processBuffer()
+ }
+
+ if (done) {
+ buffer += decoder.decode()
+ processBuffer(true)
+ break
+ }
+ }
+ } finally {
+ reader.releaseLock()
+ }
+}
diff --git a/apps/web/src/utils/types.ts b/apps/web/src/utils/types.ts
new file mode 100644
index 0000000..4ab9426
--- /dev/null
+++ b/apps/web/src/utils/types.ts
@@ -0,0 +1,790 @@
+import { ContestStatus, ContestType, LANGUAGE_SHOW_VALUE } from "./constants"
+
+export interface Profile {
+ id: number
+ user: User
+ real_name: string
+ acm_problems_status: {
+ problems: {
+ [key: string]: {
+ _id: string
+ status: number
+ }
+ }
+ }
+ avatar: string
+ blog: null
+ mood: string
+ github: string
+ school: string
+ major: string
+ language: string
+ accepted_number: number
+ submission_number: number
+}
+
+export type UserAdminType =
+ "Regular User" | "Student Admin" | "Teacher Admin" | "Super Admin"
+
+export interface User {
+ id: number
+ username: string
+ real_name: string
+ email: string
+ admin_type: UserAdminType
+ problem_permission: string
+ create_time: Date
+ last_login: Date
+ open_api: boolean
+ is_disabled: boolean
+ password?: string
+ raw_password?: string
+ class_name?: string | null
+}
+
+export type LANGUAGE =
+ | "C"
+ | "C++"
+ | "Python2"
+ | "Python3"
+ | "Java"
+ | "JavaScript"
+ | "Golang"
+ | "Flowchart"
+ | "SQL"
+
+export interface SQLConfig {
+ mode: "query" | "modify"
+ order_sensitive: boolean
+}
+
+export interface SQLDisplayColumn {
+ name: string
+ type?: string
+}
+
+export interface SQLDisplayTable {
+ name: string
+ columns: SQLDisplayColumn[]
+ rows: (string | number | null)[][]
+ total_rows: number
+ truncated: boolean
+ dropped?: boolean
+}
+
+export interface SQLDisplay {
+ tables: SQLDisplayTable[]
+ expected:
+ | {
+ columns: SQLDisplayColumn[]
+ rows: (string | number | null)[][]
+ total_rows: number
+ truncated: boolean
+ }
+ | { changed_tables: SQLDisplayTable[] }
+}
+
+export type LANGUAGE_SHOW_LABEL =
+ (typeof LANGUAGE_SHOW_VALUE)[keyof typeof LANGUAGE_SHOW_VALUE]
+
+export type SUBMISSION_RESULT =
+ -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
+
+export type ProblemStatus = "passed" | "failed" | "not_test"
+
+interface SampleUser {
+ id: number
+ username: string
+ real_name: string | null
+}
+
+export interface Tag {
+ id: number
+ name: string
+}
+
+export interface AdminTag {
+ id: number
+ name: string
+ problem_count: number
+}
+
+export interface TestcaseUploadedReturns {
+ id: string
+ info: Testcase[]
+}
+
+export interface Testcase {
+ input_name: string
+ output_name: string
+ score: string
+}
+
+export interface Problem {
+ _id: string
+ id: number
+ tags: string[]
+ created_by: SampleUser
+ template: { [key in LANGUAGE]?: string }
+ title: string
+ description: string
+ input_description: string
+ output_description: string
+ samples: {
+ input: string
+ output: string
+ }[]
+ hint: string
+ languages: Array
+ create_time: Date
+ last_update_time: null
+ time_limit: number
+ memory_limit: number
+ difficulty: "Low" | "Mid" | "High"
+ source: string
+ prompt: string
+ answers: { language: LANGUAGE; code: string }[]
+ submission_number: number
+ accepted_number: number
+ statistic_info: { [key: string]: number }
+ share_submission: boolean
+ contest: number
+ my_status: number
+ my_failed_count?: number
+ visible: boolean
+
+ // 流程图相关字段
+ allow_flowchart: boolean
+ mermaid_code?: string
+ flowchart_data?: Record
+ flowchart_hint?: string
+ show_flowchart?: boolean
+ ast_rules?: {
+ [key: string]: {
+ engine: string
+ target?: string
+ min?: number
+ max?: number
+ message: string
+ }[]
+ } | null
+ has_ast_rules?: boolean
+
+ // SQL 题配置(非 SQL 题为 null)
+ sql_config?: SQLConfig | null
+
+ // SQL 题展示数据(后端保存题目时自动生成)
+ sql_display?: SQLDisplay | null
+}
+
+export type AdminProblem = Problem &
+ AlterProblem & {
+ // 后台题目列表接口附带的最高票评价,比赛题目列表不返回
+ top_reaction?: { type: ReactionKey; count: number } | null
+ }
+
+interface AlterProblem {
+ test_case_id: string
+ test_case_score: Testcase[]
+ contest_id?: string
+}
+
+type ExcludeKeys =
+ | "id"
+ | "created_by"
+ | "create_time"
+ | "last_update_time"
+ | "my_status"
+ | "contest"
+ | "statistic_info"
+ | "accepted_number"
+ | "submission_number"
+
+export type BlankProblem = Omit &
+ AlterProblem & { id?: number }
+
+export interface ProblemFiltered {
+ _id: string
+ id: number
+ title: string
+ difficulty: "简单" | "中等" | "困难"
+ tags: string[]
+ submission: number
+ rate: string
+ status: "not_test" | "passed" | "failed"
+ author: string
+ allow_flowchart: boolean
+ show_flowchart: boolean
+ has_ast_rules: boolean
+}
+
+export interface AdminProblemFiltered {
+ _id: string
+ id: number
+ title: string
+ visible: boolean
+ username: string
+ create_time: string
+ difficulty: "Low" | "Mid" | "High"
+ tags: string[]
+ has_ast_rules: boolean
+ allow_flowchart: boolean
+ show_flowchart: boolean
+ // 比赛题目列表接口不返回这个字段
+ top_reaction?: { type: ReactionKey; count: number } | null
+}
+
+// 题单相关类型
+export interface ProblemSet {
+ id: number
+ title: string
+ description: string
+ created_by: SampleUser
+ create_time: Date
+ difficulty: "Easy" | "Medium" | "Hard"
+ status: "active" | "archived" | "draft"
+ end_time: Date | null
+ visible: boolean
+ problems_count: number
+ completed_count: number
+ user_progress: {
+ is_joined: boolean
+ progress_percentage: number
+ completed_count: number
+ total_count: number
+ is_completed: boolean
+ }
+}
+
+export interface ProblemSetList {
+ id: number
+ title: string
+ description: string
+ created_by: SampleUser
+ create_time: Date
+ difficulty: "Easy" | "Medium" | "Hard"
+ status: "active" | "archived" | "draft"
+ end_time: Date | null
+ problems_count: number
+ visible: boolean
+ user_progress: {
+ is_joined: boolean
+ progress_percentage: number
+ completed_count: number
+ total_count: number
+ is_completed: boolean
+ }
+ badges: ProblemSetBadge[]
+}
+
+export interface ProblemSetProblem {
+ id: number
+ problemset: number
+ problem: Problem
+ order: number
+ is_required: boolean
+ score: number
+ hint: string
+ is_completed: boolean
+}
+
+export interface ProblemSetBadge {
+ id: number
+ problemset: number
+ name: string
+ description: string
+ icon: string
+ condition_type: "all_problems" | "problem_count" | "score"
+ condition_value: number
+ is_earned?: boolean
+}
+
+export interface UserBadge {
+ id: number
+ user: number
+ badge: ProblemSetBadge
+ earned_time: Date
+}
+
+export interface CompletedProblem {
+ id: number
+ _id: string
+ title: string
+}
+
+export interface ProblemSetProgress {
+ id: number
+ problemset: ProblemSetList
+ user: SampleUser
+ join_time: Date
+ completed_problems_count: number
+ total_problems_count: number
+ progress_percentage: number
+ is_completed: boolean
+ completed_problems: CompletedProblem[]
+}
+
+export interface CreateProblemSetData {
+ title: string
+ description: string
+ difficulty: "Easy" | "Medium" | "Hard"
+ status: "active" | "archived" | "draft"
+ end_time?: Date | null
+}
+
+export interface EditProblemSetData {
+ id: number
+ title?: string
+ description?: string
+ difficulty?: "Easy" | "Medium" | "Hard"
+ status?: "active" | "archived" | "draft"
+ end_time?: Date | null
+ visible?: boolean
+}
+
+export interface Code {
+ language: LANGUAGE
+ value: string
+}
+
+export interface SubmitCodePayload {
+ problem_id: number
+ language: LANGUAGE
+ code: string
+ contest_id?: number
+}
+
+// ==================== 流程图相关类型 ====================
+
+export const FlowchartSubmissionStatus = {
+ PENDING: 0, // 等待AI评分
+ PROCESSING: 1, // AI评分中
+ COMPLETED: 2, // 评分完成
+ FAILED: 3, // 评分失败
+} as const
+
+export interface FlowchartSubmission {
+ id: string
+ user: number
+ problem: number
+ mermaid_code: string
+ flowchart_data: Record
+ status: number
+ create_time: string
+ ai_score?: number
+ ai_grade?: string
+ ai_feedback?: string
+ ai_suggestions?: string
+ ai_criteria_details: Record
+ ai_provider?: string
+ ai_model?: string
+ processing_time?: number
+ evaluation_time?: string
+}
+
+// 列表接口返回的字段(包含 username 和 problem_title)
+export interface FlowchartSubmissionListItem {
+ id: string
+ create_time: string
+ evaluation_time: string
+ ai_score: number
+ ai_grade: Grade
+ ai_model: string
+ ai_provider: string
+ processing_time: number
+ status: number
+ username: string
+ problem_title: string
+ problem: string
+ show_link: boolean
+}
+export interface SubmitFlowchartPayload {
+ problem_id: number
+ mermaid_code: string
+ flowchart_data?: Record
+}
+
+interface Info {
+ err: string | null
+ data: {
+ error: number
+ memory: number
+ output: null
+ result: SUBMISSION_RESULT
+ signal: number
+ cpu_time: number
+ exit_code: number
+ real_time: number
+ test_case: string
+ output_md5: string
+ }[]
+}
+
+export interface Submission {
+ id: string
+ create_time: Date
+ user_id: number
+ username: string
+ code: string
+ result: SUBMISSION_RESULT
+ info: Info
+ language: LANGUAGE
+ shared: boolean
+ show_link: boolean
+ statistic_info: {
+ score?: number
+ err_info?: string
+ time_cost?: number
+ memory_cost?: number
+ ast_results?: Array<{ description: string; passed: boolean }>
+ }
+ ip: string
+ contest: number
+ problem: number // 不是 display_id
+ can_unshare: boolean
+}
+
+export interface SubmissionListItem {
+ id: string
+ problem: string
+ problem_title: string
+ show_link: boolean
+ create_time: string
+ user_id: number
+ username: string
+ result: SUBMISSION_RESULT
+ language: LANGUAGE
+ shared: boolean
+ statistic_info: {
+ time_cost: number
+ memory_cost: number
+ }
+}
+
+export interface SubmissionListPayload {
+ myself?: "1" | "0"
+ result?: string
+ username?: string
+ contest_id?: string
+ problem_id?: string
+ language: LANGUAGE | ""
+ today?: "1" | "0"
+ page: number
+ limit: number
+ offset: number
+}
+
+export interface Rank {
+ id: number
+ user: SampleUser
+ acm_problems_status: {
+ problems: {
+ [key: string]: {
+ _id: string
+ status: number
+ }
+ }
+ contest_problems?: {
+ [key: string]: {
+ [key: string]: {
+ _id: string
+ status: number
+ }
+ }
+ }
+ }
+ real_name: null | string
+ avatar: string
+ blog: null
+ mood: null | string
+ github: null
+ school: null | string
+ major: null | string
+ language: null | string
+ accepted_number: number
+ submission_number: number
+}
+
+export interface Contest extends BlankContest {
+ id: number
+ created_by: SampleUser
+ status: ContestStatus
+ contest_type: ContestType
+ create_time: string
+ now: string
+ last_update_time: string
+}
+
+export interface BlankContest {
+ title: string
+ description: string
+ tag: string
+ start_time: string
+ end_time: string
+ password: string
+ visible: boolean
+ allowed_ip_ranges: { value: string }[]
+}
+
+interface SubmissionInfo {
+ is_ac: boolean
+ ac_time: number
+ is_first_ac: boolean
+ error_number: number
+ checked?: boolean
+}
+
+export interface ContestRank {
+ id: number
+ user: SampleUser
+ submission_number: number
+ accepted_number: number
+ total_time: number
+ submission_info: { [key: string]: SubmissionInfo }
+ contest: number
+}
+
+export interface WebsiteConfig {
+ website_base_url: string
+ website_name: string
+ website_name_shortcut: string
+ website_footer: string
+ allow_register: boolean
+ submission_list_show_all: boolean
+ class_list: string[] & never[]
+ enable_maxkb: boolean
+}
+
+export interface Server {
+ id: number
+ status: "abnormal" | "normal"
+ hostname: string
+ ip: string
+ judger_version: string
+ cpu_core: number
+ memory_usage: number
+ cpu_usage: number
+ last_heartbeat: Date
+ create_time: Date
+ task_number: number
+ service_url: string
+ is_disabled: boolean
+}
+
+export interface AnnouncementEdit {
+ id: number
+ title: string
+ tag: string
+ content: string
+ visible: boolean
+ top: boolean
+}
+
+export interface Announcement extends AnnouncementEdit {
+ created_by: SampleUser
+ create_time: Date
+ last_update_time: Date
+}
+
+export interface Message {
+ sender: User
+ create_time: Date
+ message: string
+ submission: Submission
+}
+
+export interface CreateMessage {
+ sender: string
+ recipient: string
+ submission: string
+ message: string
+}
+
+export type ReactionKey =
+ | "too_easy"
+ | "too_hard"
+ | "confusing"
+ | "buggy"
+ | "learned"
+ | "interesting"
+ | "want_explain"
+
+export type ReactionCounts = Record
+
+export interface ReactionState {
+ mine: ReactionKey | null
+ counts: ReactionCounts | null
+}
+
+export interface Tutorial {
+ id: number
+ title: string
+ content: string
+ code: string
+ is_public: boolean
+ order: number
+ type: "python" | "c"
+ created_by?: User
+ updated_at?: Date
+ created_at?: Date
+}
+
+export interface ExerciseMcqData {
+ question: string
+ options: string[]
+ answer: number[]
+}
+
+export interface ExerciseSortData {
+ question: string
+ lines: string[]
+}
+
+export interface ExerciseFillData {
+ question: string
+ code: string
+}
+
+export interface ExerciseMatchData {
+ question: string
+ left: string[]
+ right: string[]
+ answer: number[]
+}
+
+export interface ExercisePredictData {
+ question: string
+ code: string
+ answer: string[]
+}
+
+export interface ExerciseDebugData {
+ question: string
+ lines: string[]
+ answer: number[]
+ explanation?: string
+}
+
+export interface ExerciseGroupData {
+ question: string
+ buckets: string[]
+ items: string[]
+ answer: number[]
+}
+
+export type ExerciseType =
+ "mcq" | "sort" | "fill" | "match" | "predict" | "debug" | "group"
+
+export interface Exercise {
+ id: number
+ type: ExerciseType
+ data:
+ | ExerciseMcqData
+ | ExerciseSortData
+ | ExerciseFillData
+ | ExerciseMatchData
+ | ExercisePredictData
+ | ExerciseDebugData
+ | ExerciseGroupData
+ order: number
+}
+
+export interface DurationData {
+ unit: string
+ index: number
+ start: string
+ end: string
+ grade: Grade
+ problem_count: number
+ submission_count: number
+}
+
+export interface SolvedProblem {
+ problem: {
+ title: string
+ display_id: string
+ contest_title: string
+ contest_id: number
+ }
+ ac_time: string
+ rank: number
+ ac_count: number
+ grade: Grade
+ period_rank: number
+ period_ac_count: number
+ difficulty: string
+}
+
+export interface FlowchartSummary {
+ problem__id: string
+ problem_title: string
+ submission_count: number
+ best_score: number
+ best_grade: string
+ latest_submission_time: string
+ avg_score: number
+}
+
+export interface DetailsData {
+ start: string
+ end: string
+ grade: Grade
+ class_name: string
+ tags: { [key: string]: number }
+ difficulty: { [key: string]: number }
+ contest_count: number
+ solved: SolvedProblem[]
+ flowcharts: FlowchartSummary[]
+}
+
+export type Grade = "S" | "A" | "B" | "C"
+
+// ==================== 成就相关类型 ====================
+
+export type AchievementRarity = "bronze" | "silver" | "gold" | "platinum"
+
+export interface Achievement {
+ id: number
+ name: string
+ description: string
+ icon: string
+ rarity: AchievementRarity
+ hidden: boolean
+ // 隐藏成就未解锁时,后端已做掩码处理,以下四个字段为 null
+ metric: string | null
+ operator: "gte" | "lte" | null
+ threshold: number | null
+ unlocked: boolean
+ unlock_time: string | null
+ backfilled: boolean
+ progress: number | null
+ unlock_rate: number
+}
+
+export interface AchievementRarityStat {
+ rarity: AchievementRarity
+ label: string
+ total: number
+ unlocked: number
+}
+
+export interface PendingAchievement {
+ id: number
+ name: string
+ description: string
+ icon: string
+ rarity: AchievementRarity
+ // 弹窗队列里混着两种东西:全站成就和题单奖章。它们的 id 来自两张不同的表,
+ // 数值会重叠,所以去重和标记已读都必须带上 kind 一起判断。
+ // pending 接口只返回成就,不带这个字段,缺省按 achievement 处理。
+ kind?: "achievement" | "badge"
+}
+
+export interface AchievementSummary {
+ username: string
+ total: number
+ unlocked: number
+ percent: number
+ rarity: AchievementRarityStat[]
+ recent: PendingAchievement[]
+}
diff --git a/apps/web/tests/submitButtonState.test.ts b/apps/web/tests/submitButtonState.test.ts
new file mode 100644
index 0000000..d3f3735
--- /dev/null
+++ b/apps/web/tests/submitButtonState.test.ts
@@ -0,0 +1,51 @@
+import assert from "node:assert/strict"
+import test from "node:test"
+import { getSubmitButtonState } from "../src/oj/problem/components/submitButtonState.ts"
+
+const idleInput = {
+ isAuthed: true,
+ hasCode: true,
+ isFormatting: false,
+ isSubmitting: false,
+ isJudging: false,
+ isCooldown: false,
+}
+
+test("shows a disabled loading state while formatting", () => {
+ assert.deepEqual(getSubmitButtonState({ ...idleInput, isFormatting: true }), {
+ disabled: true,
+ label: "格式化中",
+ icon: "eos-icons:loading",
+ })
+})
+
+test("shows submitting immediately after formatting", () => {
+ assert.deepEqual(getSubmitButtonState({ ...idleInput, isSubmitting: true }), {
+ disabled: true,
+ label: "正在提交",
+ icon: "eos-icons:loading",
+ })
+})
+
+test("preserves existing login, judging, cooldown, and idle states", () => {
+ assert.deepEqual(getSubmitButtonState({ ...idleInput, isAuthed: false }), {
+ disabled: true,
+ label: "请先登录",
+ icon: "ph:play-fill",
+ })
+ assert.deepEqual(getSubmitButtonState({ ...idleInput, isJudging: true }), {
+ disabled: true,
+ label: "正在评分",
+ icon: "eos-icons:loading",
+ })
+ assert.deepEqual(getSubmitButtonState({ ...idleInput, isCooldown: true }), {
+ disabled: true,
+ label: "正在冷却",
+ icon: "ph:lightbulb-fill",
+ })
+ assert.deepEqual(getSubmitButtonState(idleInput), {
+ disabled: false,
+ label: "提交代码",
+ icon: "ph:play-fill",
+ })
+})
diff --git a/apps/web/tsconfig.app.json b/apps/web/tsconfig.app.json
new file mode 100644
index 0000000..47335e5
--- /dev/null
+++ b/apps/web/tsconfig.app.json
@@ -0,0 +1,23 @@
+{
+ "extends": "@vue/tsconfig/tsconfig.dom.json",
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ // Vite(oxc) 遵守 verbatimModuleSyntax,会保留未写 type 的纯类型导入导致构建报错;
+ // 关掉以让转换器自动消除类型导入(与原 Rsbuild 行为一致)
+ "verbatimModuleSyntax": false,
+ "paths": {
+ "utils/*": ["./src/utils/*"],
+ "oj/*": ["./src/oj/*"],
+ "admin/*": ["./src/admin/*"],
+ "shared/*": ["./src/shared/*"]
+ },
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src/**/*.ts", "src/**/*.vue"]
+}
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
new file mode 100644
index 0000000..1ffef60
--- /dev/null
+++ b/apps/web/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/apps/web/tsconfig.node.json b/apps/web/tsconfig.node.json
new file mode 100644
index 0000000..db0becc
--- /dev/null
+++ b/apps/web/tsconfig.node.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2022",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts
new file mode 100644
index 0000000..50db440
--- /dev/null
+++ b/apps/web/vite.config.ts
@@ -0,0 +1,175 @@
+import { fileURLToPath, URL } from "node:url"
+import { defineConfig, loadEnv, type Plugin } from "vite"
+import vue from "@vitejs/plugin-vue"
+import legacy from "@vitejs/plugin-legacy"
+import AutoImport from "unplugin-auto-import/vite"
+import Components from "unplugin-vue-components/vite"
+import { NaiveUiResolver } from "unplugin-vue-components/resolvers"
+
+// 显式保留 Chrome 90 所需的运行时兼容项,避免 plugin-legacy 对每个产物执行 Babel 扫描。
+// 升级前端依赖后需要重新审计此列表。
+const polyfills = [
+ "es.aggregate-error.cause",
+ "es.array-buffer.detached",
+ "es.array-buffer.transfer-to-fixed-length",
+ "es.array-buffer.transfer",
+ "es.array.at",
+ "es.array.find-last-index",
+ "es.array.push",
+ "es.array.to-reversed",
+ "es.array.to-sorted",
+ "es.array.to-spliced",
+ "es.array.with",
+ "es.error.cause",
+ "es.iterator.constructor",
+ "es.iterator.drop",
+ "es.iterator.every",
+ "es.iterator.filter",
+ "es.iterator.find",
+ "es.iterator.flat-map",
+ "es.iterator.for-each",
+ "es.iterator.map",
+ "es.iterator.reduce",
+ "es.iterator.some",
+ "es.iterator.to-array",
+ "es.json.parse",
+ "es.json.stringify",
+ "es.map.get-or-insert-computed",
+ "es.map.get-or-insert",
+ "es.object.has-own",
+ "es.regexp.flags",
+ "es.set.difference.v2",
+ "es.set.intersection.v2",
+ "es.set.is-disjoint-from.v2",
+ "es.set.is-subset-of.v2",
+ "es.set.is-superset-of.v2",
+ "es.set.symmetric-difference.v2",
+ "es.set.union.v2",
+ "es.string.at-alternative",
+ "es.typed-array.at",
+ "es.typed-array.find-last-index",
+ "es.typed-array.find-last",
+ "es.typed-array.set",
+ "es.typed-array.to-reversed",
+ "es.typed-array.to-sorted",
+ "es.typed-array.with",
+ "es.uint8-array.set-from-base64",
+ "es.uint8-array.set-from-hex",
+ "es.uint8-array.to-base64",
+ "es.uint8-array.to-hex",
+ "es.weak-map.get-or-insert-computed",
+ "es.weak-map.get-or-insert",
+ "esnext.array.group",
+ "web.dom-exception.stack",
+ "web.immediate",
+ "web.structured-clone",
+ "web.url-search-params.delete",
+ "web.url-search-params.has",
+ "web.url-search-params.size",
+ "web.url.can-parse",
+]
+
+// index.html 里按需注入 MaxKB 脚本(原 Rsbuild EJS 模板的等价实现)
+function injectMaxkb(maxkbUrl: string | undefined): Plugin {
+ return {
+ name: "inject-maxkb",
+ transformIndexHtml(html) {
+ if (!maxkbUrl) return html
+ return {
+ html,
+ tags: [
+ {
+ tag: "script",
+ attrs: { async: true, defer: true, src: maxkbUrl },
+ injectTo: "head",
+ },
+ ],
+ }
+ },
+ }
+}
+
+export default defineConfig(({ mode }) => {
+ const env = loadEnv(mode, process.cwd(), "PUBLIC_")
+
+ const proxyConfig = {
+ target: env["PUBLIC_OJ_URL"],
+ changeOrigin: true,
+ }
+
+ const wsProxyConfig = {
+ target: env["PUBLIC_WS_URL"],
+ ws: true,
+ changeOrigin: true,
+ }
+
+ return {
+ plugins: [
+ vue(),
+ // 机房存在 Chrome 91 等旧浏览器:不做 SystemJS 双构建,
+ // 只按使用情况给现代产物注入 core-js API polyfill(Array.at 等)
+ legacy({
+ renderLegacyChunks: false,
+ modernTargets: "chrome>=90",
+ modernPolyfills: polyfills,
+ }),
+ AutoImport({
+ imports: [
+ "vue",
+ "vue-router",
+ "@vueuse/core",
+ "pinia",
+ {
+ "naive-ui": [
+ "useDialog",
+ "useMessage",
+ "useNotification",
+ "useLoadingBar",
+ ],
+ },
+ {
+ from: "naive-ui",
+ imports: [
+ "DataTableColumn",
+ "FormRules",
+ "FormItemRule",
+ "SelectOption",
+ "UploadCustomRequestOptions",
+ "UploadFileInfo",
+ "MenuOption",
+ "DropdownDividerOption",
+ "DropdownOption",
+ ],
+ type: true,
+ },
+ ],
+ dts: "./src/auto-imports.d.ts",
+ }),
+ Components({
+ resolvers: [NaiveUiResolver()],
+ dts: "./src/components.d.ts",
+ }),
+ injectMaxkb(env["PUBLIC_MAXKB_URL"]),
+ ],
+ envPrefix: "PUBLIC_",
+ resolve: {
+ alias: {
+ // mermaid-legacy (mermaid@9) 写死了 UMD 路径,新版 cytoscape 的 exports
+ // 不允许 import 条件访问它,转到 ESM 产物
+ "cytoscape/dist/cytoscape.umd.js": "cytoscape/dist/cytoscape.esm.mjs",
+ utils: fileURLToPath(new URL("./src/utils", import.meta.url)),
+ oj: fileURLToPath(new URL("./src/oj", import.meta.url)),
+ admin: fileURLToPath(new URL("./src/admin", import.meta.url)),
+ shared: fileURLToPath(new URL("./src/shared", import.meta.url)),
+ },
+ },
+ server: {
+ port: 5173,
+ proxy: {
+ "/api": proxyConfig,
+ "/public": proxyConfig,
+ "/ws": wsProxyConfig,
+ },
+ },
+ }
+})
diff --git a/bun.lock b/bun.lock
index f31ddda..1d518e3 100644
--- a/bun.lock
+++ b/bun.lock
@@ -23,6 +23,64 @@
"drizzle-kit": "^0.31.10",
},
},
+ "apps/web": {
+ "name": "@oj2/web",
+ "version": "1.9.0",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.20.3",
+ "@codemirror/lang-cpp": "^6.0.3",
+ "@codemirror/lang-python": "^6.2.1",
+ "@codemirror/lang-sql": "^6.10.0",
+ "@oj2/contract": "workspace:*",
+ "@vue-flow/background": "^1.3.2",
+ "@vue-flow/controls": "^1.1.3",
+ "@vue-flow/core": "^1.48.2",
+ "@vue-flow/minimap": "^1.5.4",
+ "@vue-flow/node-resizer": "^1.5.1",
+ "@vue-flow/node-toolbar": "^1.1.1",
+ "@vueuse/core": "^14.4.0",
+ "@vueuse/router": "^14.4.0",
+ "@wangeditor-next/editor": "^6.2.0",
+ "@wangeditor-next/editor-for-vue": "^6.2.0",
+ "axios": "^1.19.0",
+ "canvas-confetti": "^1.9.4",
+ "chart.js": "^4.5.1",
+ "chartjs-chart-wordcloud": "^4.4.5",
+ "codemirror": "^6.0.2",
+ "copy-text-to-clipboard": "^3.2.2",
+ "date-fns": "^4.4.0",
+ "fflate": "^0.8.3",
+ "highlight.js": "^11.11.1",
+ "md-editor-v3": "^6.5.6",
+ "mermaid": "^11.16.1",
+ "mermaid-legacy": "npm:mermaid@^9.4.3",
+ "naive-ui": "^2.44.1",
+ "nanoid": "^6.0.1",
+ "normalize.css": "^8.0.1",
+ "pinia": "^4.0.2",
+ "skulpt": "^1.2.0",
+ "vue": "^3.5.41",
+ "vue-chartjs": "^5.3.4",
+ "vue-codemirror": "^6.1.1",
+ "vue-router": "^5.2.0",
+ "y-codemirror.next": "^0.3.5",
+ "y-webrtc": "^10.3.0",
+ "yjs": "^13.6.32",
+ },
+ "devDependencies": {
+ "@iconify/vue": "^5.0.1",
+ "@types/canvas-confetti": "^1.9.0",
+ "@types/node": "^26.1.2",
+ "@vitejs/plugin-legacy": "^8.2.3",
+ "@vitejs/plugin-vue": "^6.0.8",
+ "@vue/tsconfig": "^0.9.1",
+ "prettier": "^3.9.6",
+ "typescript": "^7.0.2",
+ "unplugin-auto-import": "^21.1.0",
+ "unplugin-vue-components": "^32.1.0",
+ "vite": "^8.2.1",
+ },
+ },
"packages/contract": {
"name": "@oj2/contract",
"version": "0.0.0",
@@ -32,202 +90,1438 @@
},
},
"packages": {
- "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
+ "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
- "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
+ "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
- "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
+ "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="],
- "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
+ "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="],
- "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
+ "@babel/generator": ["@babel/generator@8.0.0", "", { "dependencies": { "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g=="],
- "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
+ "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="],
- "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
+ "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="],
- "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
+ "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="],
- "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
+ "@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg=="],
- "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
+ "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.8", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA=="],
- "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
+ "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
- "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
+ "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg=="],
- "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
+ "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="],
- "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
+ "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="],
- "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
+ "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong=="],
- "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
+ "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
- "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
+ "@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-wrap-function": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og=="],
- "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
+ "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.29.7", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ=="],
- "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
+ "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="],
- "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
+ "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
- "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
+ "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
- "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
+ "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="],
- "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
+ "@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw=="],
- "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
+ "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="],
- "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
+ "@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="],
- "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
+ "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w=="],
- "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
+ "@babel/plugin-bugfix-safari-class-field-initializer-scope": ["@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ=="],
- "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ=="],
- "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
+ "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": ["@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA=="],
+
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-transform-optional-chaining": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw=="],
+
+ "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ["@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw=="],
+
+ "@babel/plugin-proposal-private-property-in-object": ["@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w=="],
+
+ "@babel/plugin-syntax-import-assertions": ["@babel/plugin-syntax-import-assertions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw=="],
+
+ "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg=="],
+
+ "@babel/plugin-syntax-unicode-sets-regex": ["@babel/plugin-syntax-unicode-sets-regex@7.18.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg=="],
+
+ "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ=="],
+
+ "@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-remap-async-to-generator": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA=="],
+
+ "@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-remap-async-to-generator": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w=="],
+
+ "@babel/plugin-transform-block-scoped-functions": ["@babel/plugin-transform-block-scoped-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA=="],
+
+ "@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ=="],
+
+ "@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA=="],
+
+ "@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A=="],
+
+ "@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g=="],
+
+ "@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/template": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA=="],
+
+ "@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg=="],
+
+ "@babel/plugin-transform-dotall-regex": ["@babel/plugin-transform-dotall-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ=="],
+
+ "@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ=="],
+
+ "@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA=="],
+
+ "@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg=="],
+
+ "@babel/plugin-transform-explicit-resource-management": ["@babel/plugin-transform-explicit-resource-management@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw=="],
+
+ "@babel/plugin-transform-exponentiation-operator": ["@babel/plugin-transform-exponentiation-operator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ=="],
+
+ "@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA=="],
+
+ "@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ=="],
+
+ "@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.29.7", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg=="],
+
+ "@babel/plugin-transform-json-strings": ["@babel/plugin-transform-json-strings@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg=="],
+
+ "@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw=="],
+
+ "@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q=="],
+
+ "@babel/plugin-transform-member-expression-literals": ["@babel/plugin-transform-member-expression-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg=="],
+
+ "@babel/plugin-transform-modules-amd": ["@babel/plugin-transform-modules-amd@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew=="],
+
+ "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ=="],
+
+ "@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.29.8", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.8" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA=="],
+
+ "@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA=="],
+
+ "@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ=="],
+
+ "@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A=="],
+
+ "@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg=="],
+
+ "@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw=="],
+
+ "@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.29.7", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7", "@babel/plugin-transform-parameters": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A=="],
+
+ "@babel/plugin-transform-object-super": ["@babel/plugin-transform-object-super@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA=="],
+
+ "@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng=="],
+
+ "@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ=="],
+
+ "@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g=="],
+
+ "@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug=="],
+
+ "@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA=="],
+
+ "@babel/plugin-transform-property-literals": ["@babel/plugin-transform-property-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA=="],
+
+ "@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.29.8", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg=="],
+
+ "@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ=="],
+
+ "@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA=="],
+
+ "@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg=="],
+
+ "@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.29.8", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA=="],
+
+ "@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA=="],
+
+ "@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA=="],
+
+ "@babel/plugin-transform-typeof-symbol": ["@babel/plugin-transform-typeof-symbol@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A=="],
+
+ "@babel/plugin-transform-unicode-escapes": ["@babel/plugin-transform-unicode-escapes@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA=="],
+
+ "@babel/plugin-transform-unicode-property-regex": ["@babel/plugin-transform-unicode-property-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw=="],
+
+ "@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA=="],
+
+ "@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg=="],
+
+ "@babel/preset-env": ["@babel/preset-env@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.29.7", "@babel/plugin-syntax-import-attributes": "^7.29.7", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.29.7", "@babel/plugin-transform-async-generator-functions": "^7.29.7", "@babel/plugin-transform-async-to-generator": "^7.29.7", "@babel/plugin-transform-block-scoped-functions": "^7.29.7", "@babel/plugin-transform-block-scoping": "^7.29.7", "@babel/plugin-transform-class-properties": "^7.29.7", "@babel/plugin-transform-class-static-block": "^7.29.7", "@babel/plugin-transform-classes": "^7.29.7", "@babel/plugin-transform-computed-properties": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7", "@babel/plugin-transform-dotall-regex": "^7.29.7", "@babel/plugin-transform-duplicate-keys": "^7.29.7", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", "@babel/plugin-transform-dynamic-import": "^7.29.7", "@babel/plugin-transform-explicit-resource-management": "^7.29.7", "@babel/plugin-transform-exponentiation-operator": "^7.29.7", "@babel/plugin-transform-export-namespace-from": "^7.29.7", "@babel/plugin-transform-for-of": "^7.29.7", "@babel/plugin-transform-function-name": "^7.29.7", "@babel/plugin-transform-json-strings": "^7.29.7", "@babel/plugin-transform-literals": "^7.29.7", "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", "@babel/plugin-transform-member-expression-literals": "^7.29.7", "@babel/plugin-transform-modules-amd": "^7.29.7", "@babel/plugin-transform-modules-commonjs": "^7.29.7", "@babel/plugin-transform-modules-systemjs": "^7.29.7", "@babel/plugin-transform-modules-umd": "^7.29.7", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", "@babel/plugin-transform-new-target": "^7.29.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", "@babel/plugin-transform-numeric-separator": "^7.29.7", "@babel/plugin-transform-object-rest-spread": "^7.29.7", "@babel/plugin-transform-object-super": "^7.29.7", "@babel/plugin-transform-optional-catch-binding": "^7.29.7", "@babel/plugin-transform-optional-chaining": "^7.29.7", "@babel/plugin-transform-parameters": "^7.29.7", "@babel/plugin-transform-private-methods": "^7.29.7", "@babel/plugin-transform-private-property-in-object": "^7.29.7", "@babel/plugin-transform-property-literals": "^7.29.7", "@babel/plugin-transform-regenerator": "^7.29.7", "@babel/plugin-transform-regexp-modifiers": "^7.29.7", "@babel/plugin-transform-reserved-words": "^7.29.7", "@babel/plugin-transform-shorthand-properties": "^7.29.7", "@babel/plugin-transform-spread": "^7.29.7", "@babel/plugin-transform-sticky-regex": "^7.29.7", "@babel/plugin-transform-template-literals": "^7.29.7", "@babel/plugin-transform-typeof-symbol": "^7.29.7", "@babel/plugin-transform-unicode-escapes": "^7.29.7", "@babel/plugin-transform-unicode-property-regex": "^7.29.7", "@babel/plugin-transform-unicode-regex": "^7.29.7", "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", "babel-plugin-polyfill-regenerator": "^0.6.6", "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA=="],
+
+ "@babel/preset-modules": ["@babel/preset-modules@0.1.6-no-external-plugins", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA=="],
+
+ "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
+
+ "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
+
+ "@babel/traverse": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="],
+
+ "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="],
+
+ "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="],
+
+ "@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="],
+
+ "@codemirror/autocomplete": ["@codemirror/autocomplete@6.20.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g=="],
+
+ "@codemirror/commands": ["@codemirror/commands@6.10.4", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.7.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-Ryk9y9T0FFVF0cUGhAknveAyUOl/A1qReTFi+qPKtOh2Z9F4AUBz3XOrYD4ZEgZirdugVzHvd/2/Wcwy5OliTg=="],
+
+ "@codemirror/lang-angular": ["@codemirror/lang-angular@0.1.4", "", { "dependencies": { "@codemirror/lang-html": "^6.0.0", "@codemirror/lang-javascript": "^6.1.2", "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.3" } }, "sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g=="],
+
+ "@codemirror/lang-cpp": ["@codemirror/lang-cpp@6.0.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/cpp": "^1.0.0" } }, "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA=="],
+
+ "@codemirror/lang-css": ["@codemirror/lang-css@6.3.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.2", "@lezer/css": "^1.1.7" } }, "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg=="],
+
+ "@codemirror/lang-go": ["@codemirror/lang-go@6.0.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.6.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/go": "^1.0.0" } }, "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg=="],
+
+ "@codemirror/lang-html": ["@codemirror/lang-html@6.4.12", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-css": "^6.0.0", "@codemirror/lang-javascript": "^6.0.0", "@codemirror/language": "^6.4.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/css": "^1.1.0", "@lezer/html": "^1.3.12" } }, "sha512-pw2ReWKUqSkbvh76RAT4NYxiogRu+PWkR2ukAwO9uOgrm8uipkzjtKKtNpyeAQwHOqxEeSvAXZ6vr3AfyB9y/w=="],
+
+ "@codemirror/lang-java": ["@codemirror/lang-java@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/java": "^1.0.0" } }, "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ=="],
+
+ "@codemirror/lang-javascript": ["@codemirror/lang-javascript@6.2.5", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.6.0", "@codemirror/lint": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/javascript": "^1.0.0" } }, "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A=="],
+
+ "@codemirror/lang-jinja": ["@codemirror/lang-jinja@6.0.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.2.0", "@lezer/lr": "^1.4.0" } }, "sha512-P5kyHLObzjtbGj16h+hyvZTxJhSjBEeSx4wMjbnAf3b0uwTy2+F0zGjMZL4PQOm/mh2eGZ5xUDVZXgwP783Nsw=="],
+
+ "@codemirror/lang-json": ["@codemirror/lang-json@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/json": "^1.0.0" } }, "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ=="],
+
+ "@codemirror/lang-less": ["@codemirror/lang-less@6.0.2", "", { "dependencies": { "@codemirror/lang-css": "^6.2.0", "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ=="],
+
+ "@codemirror/lang-liquid": ["@codemirror/lang-liquid@6.3.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.1" } }, "sha512-6PDVU3ZnfeYyz1at1E/ttorErZvZFXXt1OPhtfe1EZJ2V2iDFa0CwPqPgG5F7NXN0yONGoBogKmFAafKTqlwIw=="],
+
+ "@codemirror/lang-markdown": ["@codemirror/lang-markdown@6.5.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.7.1", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.3.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.2.1", "@lezer/markdown": "^1.0.0" } }, "sha512-AwBOdkWYuA//WcM0xO5PfHPUcmz/O2i5o0Nsg1U69SII/loCJlFI1Romd9xp2HYb1kYJRGZotyqRghuHH5n8Kw=="],
+
+ "@codemirror/lang-php": ["@codemirror/lang-php@6.0.2", "", { "dependencies": { "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/php": "^1.0.0" } }, "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA=="],
+
+ "@codemirror/lang-python": ["@codemirror/lang-python@6.2.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.3.2", "@codemirror/language": "^6.8.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.2.1", "@lezer/python": "^1.1.4" } }, "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw=="],
+
+ "@codemirror/lang-rust": ["@codemirror/lang-rust@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/rust": "^1.0.0" } }, "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA=="],
+
+ "@codemirror/lang-sass": ["@codemirror/lang-sass@6.0.2", "", { "dependencies": { "@codemirror/lang-css": "^6.2.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.2", "@lezer/sass": "^1.0.0" } }, "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q=="],
+
+ "@codemirror/lang-sql": ["@codemirror/lang-sql@6.10.0", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w=="],
+
+ "@codemirror/lang-vue": ["@codemirror/lang-vue@0.1.3", "", { "dependencies": { "@codemirror/lang-html": "^6.0.0", "@codemirror/lang-javascript": "^6.1.2", "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.1" } }, "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug=="],
+
+ "@codemirror/lang-wast": ["@codemirror/lang-wast@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q=="],
+
+ "@codemirror/lang-xml": ["@codemirror/lang-xml@6.1.0", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.4.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/xml": "^1.0.0" } }, "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg=="],
+
+ "@codemirror/lang-yaml": ["@codemirror/lang-yaml@6.1.3", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.2.0", "@lezer/lr": "^1.0.0", "@lezer/yaml": "^1.0.0" } }, "sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ=="],
+
+ "@codemirror/language": ["@codemirror/language@6.12.4", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A=="],
+
+ "@codemirror/language-data": ["@codemirror/language-data@6.5.2", "", { "dependencies": { "@codemirror/lang-angular": "^0.1.0", "@codemirror/lang-cpp": "^6.0.0", "@codemirror/lang-css": "^6.0.0", "@codemirror/lang-go": "^6.0.0", "@codemirror/lang-html": "^6.0.0", "@codemirror/lang-java": "^6.0.0", "@codemirror/lang-javascript": "^6.0.0", "@codemirror/lang-jinja": "^6.0.0", "@codemirror/lang-json": "^6.0.0", "@codemirror/lang-less": "^6.0.0", "@codemirror/lang-liquid": "^6.0.0", "@codemirror/lang-markdown": "^6.0.0", "@codemirror/lang-php": "^6.0.0", "@codemirror/lang-python": "^6.0.0", "@codemirror/lang-rust": "^6.0.0", "@codemirror/lang-sass": "^6.0.0", "@codemirror/lang-sql": "^6.0.0", "@codemirror/lang-vue": "^0.1.1", "@codemirror/lang-wast": "^6.0.0", "@codemirror/lang-xml": "^6.0.0", "@codemirror/lang-yaml": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/legacy-modes": "^6.4.0" } }, "sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg=="],
+
+ "@codemirror/legacy-modes": ["@codemirror/legacy-modes@6.5.3", "", { "dependencies": { "@codemirror/language": "^6.0.0" } }, "sha512-xCsmIzH78MyWkib9jlPaaun57XNkfbMIhagfaZVd0iLTqlpw3jXaIcbZm72MTmmn64eTZpBVNjbyYh+QXnxRsg=="],
+
+ "@codemirror/lint": ["@codemirror/lint@6.9.7", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.42.0", "crelt": "^1.0.5" } }, "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg=="],
+
+ "@codemirror/search": ["@codemirror/search@6.7.1", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.37.0", "crelt": "^1.0.5" } }, "sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA=="],
+
+ "@codemirror/state": ["@codemirror/state@6.7.1", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A=="],
+
+ "@codemirror/view": ["@codemirror/view@6.43.8", "", { "dependencies": { "@codemirror/state": "^6.7.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-qtItTDssZ/5GFfi94hrILu9j/VUeFPDPkhovEfmWFj2ipTxnzPB8DdHgfbb8HYTzLTYhrndKmyQxXUz/PDLenw=="],
+
+ "@css-render/plugin-bem": ["@css-render/plugin-bem@0.15.14", "", { "peerDependencies": { "css-render": "~0.15.14" } }, "sha512-QK513CJ7yEQxm/P3EwsI+d+ha8kSOcjGvD6SevM41neEMxdULE+18iuQK6tEChAWMOQNQPLG/Rw3Khb69r5neg=="],
+
+ "@css-render/vue3-ssr": ["@css-render/vue3-ssr@0.15.14", "", { "peerDependencies": { "vue": "^3.0.11" } }, "sha512-//8027GSbxE9n3QlD73xFY6z4ZbHbvrOVB7AO6hsmrEzGbg+h2A09HboUyDgu+xsmj7JnvJD39Irt+2D0+iV8g=="],
+
+ "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "https://registry.npmjs.com/@drizzle-team/brocli/-/brocli-0.10.2.tgz", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
+
+ "@emotion/hash": ["@emotion/hash@0.8.0", "", {}, "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="],
+
+ "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "https://registry.npmjs.com/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
+
+ "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "https://registry.npmjs.com/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
+
+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "https://registry.npmjs.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
+
+ "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "https://registry.npmjs.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
+
+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
+
+ "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "https://registry.npmjs.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
+
+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
+
+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "https://registry.npmjs.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
+
+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
+
+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "https://registry.npmjs.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
+
+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "https://registry.npmjs.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
+
+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
+
+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "https://registry.npmjs.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
+
+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "https://registry.npmjs.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
+
+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "https://registry.npmjs.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
+
+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "https://registry.npmjs.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
+
+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "https://registry.npmjs.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
+
+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "https://registry.npmjs.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
+
+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "https://registry.npmjs.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
+
+ "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
+
+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "https://registry.npmjs.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
+
+ "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
+
+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "https://registry.npmjs.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
+
+ "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
+
+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "https://registry.npmjs.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
+
+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
+
+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "https://registry.npmjs.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
+
+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "https://registry.npmjs.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
+
+ "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
+
+ "@iconify/utils": ["@iconify/utils@3.1.4", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw=="],
+
+ "@iconify/vue": ["@iconify/vue@5.0.1", "", { "dependencies": { "@iconify/types": "^2.0.0" }, "peerDependencies": { "vue": ">=3.0.0" } }, "sha512-aumwwooJlFJ5H5qYWB6ZTAyM0C8hpfcSVLB9/a3qnH1GGvIJ+FEbpEs4s/HfErYe/M5qZeLjwmESR5fFm3lXEw=="],
+
+ "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
+
+ "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
+
+ "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
+
+ "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="],
+
+ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
+
+ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+
+ "@juggle/resize-observer": ["@juggle/resize-observer@3.4.0", "", {}, "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="],
+
+ "@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="],
+
+ "@lezer/common": ["@lezer/common@1.5.2", "", {}, "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ=="],
+
+ "@lezer/cpp": ["@lezer/cpp@1.1.6", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-vh9gWWJOXFVY8HBHK3Twzq8MgwG2iN4GSyzBP9sCGTe37P15x2R14VaBQk0VA0ezTRN1KHYBBsHhvpGZ2Xy/pA=="],
+
+ "@lezer/css": ["@lezer/css@1.3.6", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.0" } }, "sha512-YJE78Wcg+zX8f10hiHWQ4Az48Qr/c13eId0VtRQYLBpxHDmDeSrXIlkbl+fJGW42rWC/uoUco9mhBZeVWP/A1g=="],
+
+ "@lezer/go": ["@lezer/go@1.0.1", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.0" } }, "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ=="],
+
+ "@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="],
+
+ "@lezer/html": ["@lezer/html@1.3.13", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg=="],
+
+ "@lezer/java": ["@lezer/java@1.1.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw=="],
+
+ "@lezer/javascript": ["@lezer/javascript@1.5.4", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.1.3", "@lezer/lr": "^1.3.0" } }, "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA=="],
+
+ "@lezer/json": ["@lezer/json@1.0.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ=="],
+
+ "@lezer/lr": ["@lezer/lr@1.4.10", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A=="],
+
+ "@lezer/markdown": ["@lezer/markdown@1.7.2", "", { "dependencies": { "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0" } }, "sha512-iTkYvoVcKt3WkeL7qUDyXHONZEwLio4wj8KTNi2dnjQEXBZKMV63BpQrPqfsM+OkvuRbiSTAcycYAsQzLhRNoQ=="],
+
+ "@lezer/php": ["@lezer/php@1.0.5", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.1.0" } }, "sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA=="],
+
+ "@lezer/python": ["@lezer/python@1.1.19", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-MhQIURHRytsNzP/YXnqpYKW6la6voAH3kyplTOOiCdjyFY6cWWGFVmYVdHIPrElqSDf4iCDktQCockB9FxuhzQ=="],
+
+ "@lezer/rust": ["@lezer/rust@1.0.2", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg=="],
+
+ "@lezer/sass": ["@lezer/sass@1.1.0", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ=="],
+
+ "@lezer/xml": ["@lezer/xml@1.0.6", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww=="],
+
+ "@lezer/yaml": ["@lezer/yaml@1.0.4", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.4.0" } }, "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw=="],
+
+ "@lucide/vue": ["@lucide/vue@1.29.0", "", { "peerDependencies": { "vue": ">=3.0.1" } }, "sha512-UnbPPAdxWGqABrjcQJZFqawSgZTBmeKOrNFPI9sqKaDkgLoEbYyx+ivIdiYRR1YoYhWWg7dCdvjtCwK6zBxWDQ=="],
+
+ "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.3", "", {}, "sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA=="],
+
+ "@mermaid-js/parser": ["@mermaid-js/parser@1.2.0", "", { "dependencies": { "@chevrotain/types": "~11.1.2" } }, "sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA=="],
"@oj2/api": ["@oj2/api@workspace:apps/api"],
"@oj2/contract": ["@oj2/contract@workspace:packages/contract"],
+ "@oj2/web": ["@oj2/web@workspace:apps/web"],
+
+ "@oxc-project/types": ["@oxc-project/types@0.143.0", "", {}, "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA=="],
+
+ "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.3", "", { "os": "android", "cpu": "arm64" }, "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw=="],
+
+ "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA=="],
+
+ "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA=="],
+
+ "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg=="],
+
+ "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.3", "", { "os": "linux", "cpu": "arm" }, "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw=="],
+
+ "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw=="],
+
+ "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q=="],
+
+ "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w=="],
+
+ "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg=="],
+
+ "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w=="],
+
+ "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A=="],
+
+ "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.3", "", { "os": "none", "cpu": "arm64" }, "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug=="],
+
+ "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw=="],
+
+ "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.3", "", { "os": "win32", "cpu": "x64" }, "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg=="],
+
+ "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
+
+ "@transloadit/prettier-bytes": ["@transloadit/prettier-bytes@0.3.5", "", {}, "sha512-xF4A3d/ZyX2LJWeQZREZQw+qFX4TGQ8bGVP97OLRt6sPO6T0TNHBFTuRHOJh7RNmYOBmQ9MHxpolD9bXihpuVA=="],
+
"@types/bun": ["@types/bun@1.3.14", "https://registry.npmjs.com/@types/bun/-/bun-1.3.14.tgz", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
+ "@types/canvas-confetti": ["@types/canvas-confetti@1.9.0", "", {}, "sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg=="],
+
+ "@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="],
+
+ "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
+
+ "@types/d3-axis": ["@types/d3-axis@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw=="],
+
+ "@types/d3-brush": ["@types/d3-brush@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A=="],
+
+ "@types/d3-chord": ["@types/d3-chord@3.0.6", "", {}, "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="],
+
+ "@types/d3-cloud": ["@types/d3-cloud@1.2.9", "", { "dependencies": { "@types/d3": "^3" } }, "sha512-5EWJvnlCrqTThGp8lYHx+DL00sOjx2HTlXH1WRe93k5pfOIhPQaL63NttaKYIbT7bTXp/USiunjNS/N4ipttIQ=="],
+
+ "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
+
+ "@types/d3-contour": ["@types/d3-contour@3.0.6", "", { "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg=="],
+
+ "@types/d3-delaunay": ["@types/d3-delaunay@6.0.4", "", {}, "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="],
+
+ "@types/d3-dispatch": ["@types/d3-dispatch@3.0.7", "", {}, "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA=="],
+
+ "@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="],
+
+ "@types/d3-dsv": ["@types/d3-dsv@3.0.7", "", {}, "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="],
+
+ "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="],
+
+ "@types/d3-fetch": ["@types/d3-fetch@3.0.7", "", { "dependencies": { "@types/d3-dsv": "*" } }, "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA=="],
+
+ "@types/d3-force": ["@types/d3-force@3.0.10", "", {}, "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="],
+
+ "@types/d3-format": ["@types/d3-format@3.0.4", "", {}, "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="],
+
+ "@types/d3-geo": ["@types/d3-geo@3.1.1", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w=="],
+
+ "@types/d3-hierarchy": ["@types/d3-hierarchy@3.1.7", "", {}, "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="],
+
+ "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="],
+
+ "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="],
+
+ "@types/d3-polygon": ["@types/d3-polygon@3.0.2", "", {}, "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="],
+
+ "@types/d3-quadtree": ["@types/d3-quadtree@3.0.6", "", {}, "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="],
+
+ "@types/d3-random": ["@types/d3-random@3.0.4", "", {}, "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA=="],
+
+ "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="],
+
+ "@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="],
+
+ "@types/d3-selection": ["@types/d3-selection@3.0.11", "", {}, "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="],
+
+ "@types/d3-shape": ["@types/d3-shape@3.1.8", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="],
+
+ "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="],
+
+ "@types/d3-time-format": ["@types/d3-time-format@4.0.3", "", {}, "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg=="],
+
+ "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="],
+
+ "@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="],
+
+ "@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="],
+
+ "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
+
+ "@types/event-emitter": ["@types/event-emitter@0.3.5", "", {}, "sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ=="],
+
+ "@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
+
+ "@types/jsesc": ["@types/jsesc@2.5.1", "", {}, "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw=="],
+
+ "@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="],
+
+ "@types/lodash": ["@types/lodash@4.17.25", "", {}, "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ=="],
+
+ "@types/lodash-es": ["@types/lodash-es@4.17.12", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ=="],
+
+ "@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="],
+
+ "@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="],
+
"@types/node": ["@types/node@26.1.2", "https://registry.npmjs.com/@types/node/-/node-26.1.2.tgz", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg=="],
- "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
+ "@types/retry": ["@types/retry@0.12.2", "", {}, "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow=="],
+
+ "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
+
+ "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
+
+ "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="],
+
+ "@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="],
+
+ "@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="],
+
+ "@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="],
+
+ "@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="],
+
+ "@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="],
+
+ "@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="],
+
+ "@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="],
+
+ "@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="],
+
+ "@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="],
+
+ "@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="],
+
+ "@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="],
+
+ "@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="],
+
+ "@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="],
+
+ "@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="],
+
+ "@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="],
+
+ "@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="],
+
+ "@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="],
+
+ "@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="],
+
+ "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="],
+
+ "@uppy/companion-client": ["@uppy/companion-client@5.1.1", "", { "dependencies": { "@uppy/utils": "^7.1.1", "namespace-emitter": "^2.0.1", "p-retry": "^6.1.0" }, "peerDependencies": { "@uppy/core": "^5.1.1" } }, "sha512-DzrOWTbIZHvtgAFXBMYHk2wD27NjpBSVhY2tEiEIUhPd2CxbFRZjHM/N3HOt3VwZEAP471QWFLlJRWPcIY3A2Q=="],
+
+ "@uppy/core": ["@uppy/core@5.2.0", "", { "dependencies": { "@transloadit/prettier-bytes": "^0.3.4", "@uppy/store-default": "^5.0.0", "@uppy/utils": "^7.1.4", "lodash": "^4.17.21", "mime-match": "^1.0.2", "namespace-emitter": "^2.0.1", "nanoid": "^5.0.9", "preact": "^10.5.13" } }, "sha512-uvfNyz4cnaplt7LYJmEZHuqOuav0tKp4a9WKJIaH6iIj7XiqYvS2J5SEByexAlUFlzefOAyjzj4Ja2dd/8aMrw=="],
+
+ "@uppy/store-default": ["@uppy/store-default@5.0.0", "", {}, "sha512-hQtCSQ1yGiaval/wVYUWquYGDJ+bpQ7e4FhUUAsRQz1x1K+o7NBtjfp63O9I4Ks1WRoKunpkarZ+as09l02cPw=="],
+
+ "@uppy/utils": ["@uppy/utils@7.2.0", "", { "dependencies": { "lodash": "^4.17.23", "preact": "^10.26.10" } }, "sha512-6lC246qszMv6bTyl/+QyHwrudgeguWkA94ME1wHn+a6uRAvmtAEaUManIfGqTJfoKvWAiCJqdJPl5xRJjhAloQ=="],
+
+ "@uppy/xhr-upload": ["@uppy/xhr-upload@5.2.0", "", { "dependencies": { "@uppy/companion-client": "^5.1.1", "@uppy/utils": "^7.2.0" }, "peerDependencies": { "@uppy/core": "^5.2.0" } }, "sha512-3LV/X5Of6BINnKplP+CwUJ0a4/7cRFfzxwGyXnW+uCrNQHoo09dttcz3begWHejGvzenQHuUnMO3Fxyc71Pryg=="],
+
+ "@upsetjs/venn.js": ["@upsetjs/venn.js@2.0.0", "", { "optionalDependencies": { "d3-selection": "^3.0.0", "d3-transition": "^3.0.1" } }, "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw=="],
+
+ "@vavt/copy2clipboard": ["@vavt/copy2clipboard@1.0.3", "", {}, "sha512-HtG48r2FBYp9eRvGB3QGmtRBH1zzRRAVvFbGgFstOwz4/DDaNiX0uZc3YVKPydqgOav26pibr9MtoCaWxn7aeA=="],
+
+ "@vavt/markdown-theme": ["@vavt/markdown-theme@5.0.2", "", {}, "sha512-cGYEV2VIUmhT9p8Wua9yXh7sKN1f3cbztTFHlkYyH9SzbpiKUnQrcbDts/0fP6cmvZPOe9SWWsDujmyuHepJdQ=="],
+
+ "@vavt/util": ["@vavt/util@2.1.2", "", {}, "sha512-L3UbSJthJwr3wq0x93O5TrCepimrmVZaIl2ciZbeL18G5++gBhJXNhcH7RcVk/6rr3SavWOvwhig0mqRLoR7dw=="],
+
+ "@vitejs/plugin-legacy": ["@vitejs/plugin-legacy@8.2.3", "", { "dependencies": { "@babel/core": "^7.29.7", "@babel/plugin-transform-dynamic-import": "^7.29.7", "@babel/plugin-transform-modules-systemjs": "^7.29.8", "@babel/preset-env": "^7.29.7", "babel-plugin-polyfill-corejs3": "^0.14.2", "babel-plugin-polyfill-regenerator": "^0.6.8", "browserslist": "^4.28.7", "browserslist-to-esbuild": "^2.1.1", "core-js": "^3.49.0", "magic-string": "^1.1.0", "regenerator-runtime": "^0.14.1", "systemjs": "^6.15.1" }, "peerDependencies": { "terser": "^5.16.0", "vite": "^8.0.0" } }, "sha512-MMHLsn/0FrBkCQyb1x44cPYGwkfqcGr2FblcEP2ne79aXe7d5tFKoZ09xs5C4wuh8fOu2JRaNhgAS3K3uvUeJA=="],
+
+ "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.8", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew=="],
+
+ "@vue-flow/background": ["@vue-flow/background@1.3.2", "", { "peerDependencies": { "@vue-flow/core": "^1.23.0", "vue": "^3.3.0" } }, "sha512-eJPhDcLj1wEo45bBoqTXw1uhl0yK2RaQGnEINqvvBsAFKh/camHJd5NPmOdS1w+M9lggc9igUewxaEd3iCQX2w=="],
+
+ "@vue-flow/controls": ["@vue-flow/controls@1.1.3", "", { "peerDependencies": { "@vue-flow/core": "^1.23.0", "vue": "^3.3.0" } }, "sha512-XCf+G+jCvaWURdFlZmOjifZGw3XMhN5hHlfMGkWh9xot+9nH9gdTZtn+ldIJKtarg3B21iyHU8JjKDhYcB6JMw=="],
+
+ "@vue-flow/core": ["@vue-flow/core@1.48.2", "", { "dependencies": { "@vueuse/core": "^10.5.0", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" }, "peerDependencies": { "vue": "^3.3.0" } }, "sha512-raxhgKWE+G/mcEvXJjGFUDYW9rAI3GOtiHR3ZkNpwBWuIaCC1EYiBmKGwJOoNzVFgwO7COgErnK7i08i287AFA=="],
+
+ "@vue-flow/minimap": ["@vue-flow/minimap@1.5.4", "", { "dependencies": { "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" }, "peerDependencies": { "@vue-flow/core": "^1.23.0", "vue": "^3.3.0" } }, "sha512-l4C+XTAXnRxsRpUdN7cAVFBennC1sVRzq4bDSpVK+ag7tdMczAnhFYGgbLkUw3v3sY6gokyWwMl8CDonp8eB2g=="],
+
+ "@vue-flow/node-resizer": ["@vue-flow/node-resizer@1.5.1", "", { "dependencies": { "d3-drag": "^3.0.0", "d3-selection": "^3.0.0" }, "peerDependencies": { "@vue-flow/core": "^1.23.0", "vue": "^3.3.0" } }, "sha512-DNYgXTF39GXPAygVMFmgn5azgFztaWfqbWvv8D/X0HGgeunRrexJ0W94DyXH1rnndGyYAJ0KexEjyelsb+Nlbg=="],
+
+ "@vue-flow/node-toolbar": ["@vue-flow/node-toolbar@1.1.1", "", { "peerDependencies": { "@vue-flow/core": "^1.23.0", "vue": "^3.3.0" } }, "sha512-vgSnGMLd3FXstdpvC721qcBDzGkPsudmyA8urEP55/EMikCp59klgzPvVULTDxxsew5MdXtTBCumsqOi+PXJdg=="],
+
+ "@vue-macros/common": ["@vue-macros/common@3.1.4", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw=="],
+
+ "@vue/compiler-core": ["@vue/compiler-core@3.5.41", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.41", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg=="],
+
+ "@vue/compiler-dom": ["@vue/compiler-dom@3.5.41", "", { "dependencies": { "@vue/compiler-core": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw=="],
+
+ "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.41", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/compiler-core": "3.5.41", "@vue/compiler-dom": "3.5.41", "@vue/compiler-ssr": "3.5.41", "@vue/shared": "3.5.41", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.19", "source-map-js": "^1.2.1" } }, "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ=="],
+
+ "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.41", "", { "dependencies": { "@vue/compiler-dom": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A=="],
+
+ "@vue/devtools-api": ["@vue/devtools-api@8.2.1", "", { "dependencies": { "@vue/devtools-kit": "^8.2.1" } }, "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A=="],
+
+ "@vue/devtools-kit": ["@vue/devtools-kit@8.2.1", "", { "dependencies": { "@vue/devtools-shared": "^8.2.1", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ=="],
+
+ "@vue/devtools-shared": ["@vue/devtools-shared@8.2.1", "", {}, "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g=="],
+
+ "@vue/reactivity": ["@vue/reactivity@3.5.41", "", { "dependencies": { "@vue/shared": "3.5.41" } }, "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA=="],
+
+ "@vue/runtime-core": ["@vue/runtime-core@3.5.41", "", { "dependencies": { "@vue/reactivity": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg=="],
+
+ "@vue/runtime-dom": ["@vue/runtime-dom@3.5.41", "", { "dependencies": { "@vue/reactivity": "3.5.41", "@vue/runtime-core": "3.5.41", "@vue/shared": "3.5.41", "csstype": "^3.2.3" } }, "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw=="],
+
+ "@vue/server-renderer": ["@vue/server-renderer@3.5.41", "", { "dependencies": { "@vue/compiler-ssr": "3.5.41", "@vue/runtime-dom": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ=="],
+
+ "@vue/shared": ["@vue/shared@3.5.41", "", {}, "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA=="],
+
+ "@vue/tsconfig": ["@vue/tsconfig@0.9.1", "", { "peerDependencies": { "typescript": ">= 5.8", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w=="],
+
+ "@vueuse/core": ["@vueuse/core@14.4.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.4.0", "@vueuse/shared": "14.4.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ=="],
+
+ "@vueuse/metadata": ["@vueuse/metadata@14.4.0", "", {}, "sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g=="],
+
+ "@vueuse/router": ["@vueuse/router@14.4.0", "", { "dependencies": { "@vueuse/shared": "14.4.0" }, "peerDependencies": { "vue": "^3.5.0", "vue-router": "^4.0.0 || ^5.0.0" } }, "sha512-q+dRUKI6xYGT4vNPq2t3fujrXPw2KBFV5Jxhw38liTFzwzGXBYcf8Qdjk7AiQpjEH2xLSin6WVTvj85EEIpo9w=="],
+
+ "@vueuse/shared": ["@vueuse/shared@14.4.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g=="],
+
+ "@wangeditor-next/basic-modules": ["@wangeditor-next/basic-modules@6.2.0", "", { "dependencies": { "is-url": "^1.2.4" }, "peerDependencies": { "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "lodash.throttle": "^4.1.1", "nanoid": "^5.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-TZF9qtiMq1go5gRMTFicq4mQmaBYsO6QOCe3Z8/A7DHbDDBHNvFKe5BnXhRBBS3kp3ukQK5ZIsdeR8kBrtWY7A=="],
+
+ "@wangeditor-next/code-highlight": ["@wangeditor-next/code-highlight@6.2.0", "", { "dependencies": { "prismjs": "^1.23.0" }, "peerDependencies": { "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-7vWVazX+1uc72AG+bH/OS5UwGTYc0xLgErVEv/UbANnxP1d7eje67X2XY+tS7UH9UmirySmZoTKFtyduJSRCyg=="],
+
+ "@wangeditor-next/core": ["@wangeditor-next/core@6.2.0", "", { "dependencies": { "@types/event-emitter": "^0.3.3", "event-emitter": "^0.3.5", "html-void-elements": "^3.0.0", "i18next": "^23.0.0", "scroll-into-view-if-needed": "^3.0.0", "slate-history": "^0.115.0" }, "peerDependencies": { "@uppy/core": "^2.1.1 || ^5.0.0", "@uppy/xhr-upload": "^2.0.3 || ^5.0.0", "dom7": "^3.0.0 || ^4.0.0", "is-hotkey": "^0.2.0", "lodash.camelcase": "^4.3.0", "lodash.clonedeep": "^4.5.0", "lodash.debounce": "^4.0.8", "lodash.foreach": "^4.5.0", "lodash.throttle": "^4.1.1", "lodash.toarray": "^4.4.0", "nanoid": "^5.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-uHPgMUGJGMhV9LUhH9ZgjbfmTGowU/3G9KFHT+PdPvM7CDhdIq1gBGVbGapEWS1nLlfbx9JeOcDhFq1yBKwmaw=="],
+
+ "@wangeditor-next/editor": ["@wangeditor-next/editor@6.2.0", "", { "dependencies": { "@uppy/core": "^5.0.0", "@uppy/xhr-upload": "^5.0.0", "@wangeditor-next/basic-modules": "^6.2.0", "@wangeditor-next/code-highlight": "^6.2.0", "@wangeditor-next/core": "^6.2.0", "@wangeditor-next/list-module": "^6.2.0", "@wangeditor-next/table-module": "^6.2.0", "@wangeditor-next/upload-image-module": "^6.2.0", "@wangeditor-next/video-module": "^6.2.0", "dom7": "^4.0.0", "is-hotkey": "^0.2.0", "lodash.camelcase": "^4.3.0", "lodash.clonedeep": "^4.5.0", "lodash.debounce": "^4.0.8", "lodash.foreach": "^4.5.0", "lodash.throttle": "^4.1.1", "lodash.toarray": "^4.4.0", "nanoid": "^5.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-8x/JdD9NWAw/gd+LPtnEEVU8ef2y2/Db7DGpVnYENITyvet1tjcObBZxyG5edCe6KACgseaqklhVnpfG8xayJA=="],
+
+ "@wangeditor-next/editor-for-vue": ["@wangeditor-next/editor-for-vue@6.2.0", "", { "peerDependencies": { "@wangeditor-next/editor": "^6.2.0", "vue": "^3.0.5" } }, "sha512-c8QDYnioeaySIIsSdRwM9GL+HyvbTuy2WyaxfvSf+GHoYGe3Hi5NeA8AL8OQnJIJRSkkU0dS7CZlnjnWMD+7AA=="],
+
+ "@wangeditor-next/list-module": ["@wangeditor-next/list-module@6.2.0", "", { "peerDependencies": { "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-rmefXe7HYY0o+treOWdhq9xDLilcoQJzBZ33V1f9vp6mvlBNXvpdPfFwnNK3TrFVtUF6ELtiGA4dFe/CeajiQQ=="],
+
+ "@wangeditor-next/table-module": ["@wangeditor-next/table-module@6.2.0", "", { "peerDependencies": { "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", "nanoid": "^5.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-XAipfwV5/aYwwxQaz4Ty1EZbyOr9ssYq8NL7oqzUrQB6Atpt2MZLeS88+npEhu8V5xntqunvijIx1ohl8/uRCg=="],
+
+ "@wangeditor-next/upload-image-module": ["@wangeditor-next/upload-image-module@6.2.0", "", { "peerDependencies": { "@uppy/core": "^2.0.3 || ^5.0.0", "@uppy/xhr-upload": "^2.0.3 || ^5.0.0", "@wangeditor-next/basic-modules": "^6.2.0", "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "lodash.foreach": "^4.5.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-w65xUavqDi62kIwItnyKBjmMeBrUCKZkerACQKNU7MeLj1w71q7IPGmvi5It6dwA4Pk64sl+l0eKCsLI6dLRFA=="],
+
+ "@wangeditor-next/video-module": ["@wangeditor-next/video-module@6.2.0", "", { "peerDependencies": { "@uppy/core": "^2.1.4 || ^5.0.0", "@uppy/xhr-upload": "^2.0.7 || ^5.0.0", "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "nanoid": "^5.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-4SDqjHb+VwOSmyV+XlCBrR9SLWU0svnN7TnfDmgZHO/wUGxK6uVY0biKsn6GDpQjGL77XqiQNE/PK8YT4noY2Q=="],
+
+ "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="],
+
+ "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
+
+ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
+
+ "ast-kit": ["ast-kit@2.2.0", "", { "dependencies": { "@babel/parser": "^7.28.5", "pathe": "^2.0.3" } }, "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw=="],
+
+ "ast-walker-scope": ["ast-walker-scope@0.9.0", "", { "dependencies": { "@babel/parser": "^7.29.2", "@babel/types": "^7.29.0", "ast-kit": "^2.2.0" } }, "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A=="],
+
+ "async-validator": ["async-validator@4.2.5", "", {}, "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="],
+
+ "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
+
+ "axios": ["axios@1.19.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw=="],
+
+ "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.17", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w=="],
+
+ "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.14.2", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8", "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g=="],
+
+ "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.8", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg=="],
+
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
+
+ "baseline-browser-mapping": ["baseline-browser-mapping@2.11.12", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA=="],
+
+ "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
+
+ "browserslist": ["browserslist@4.28.7", "", { "dependencies": { "baseline-browser-mapping": "^2.10.44", "caniuse-lite": "^1.0.30001806", "electron-to-chromium": "^1.5.393", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw=="],
+
+ "browserslist-to-esbuild": ["browserslist-to-esbuild@2.1.1", "", { "dependencies": { "meow": "^13.0.0" }, "peerDependencies": { "browserslist": "*" }, "bin": { "browserslist-to-esbuild": "cli/index.js" } }, "sha512-KN+mty6C3e9AN8Z5dI1xeN15ExcRNeISoC3g7V0Kax/MMF9MSoYA2G7lkTTcVUFntiEjkpI0HNgqJC1NjdyNUw=="],
+
+ "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
+
+ "buffer-from": ["buffer-from@1.1.2", "https://registry.npmjs.com/buffer-from/-/buffer-from-1.1.2.tgz", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"bun-types": ["bun-types@1.3.14", "https://registry.npmjs.com/bun-types/-/bun-types-1.3.14.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
- "drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="],
+ "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
- "drizzle-orm": ["drizzle-orm@0.45.2", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="],
+ "caniuse-lite": ["caniuse-lite@1.0.30001807", "", {}, "sha512-daRXJ9EB/rdRgu7kV+TTl1YUKtlsMWblPl2sLnpg9DZae16QCegol6A1SmCE31Lm9mXC1sRWGt/krouH+/dl7Q=="],
- "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
+ "canvas-confetti": ["canvas-confetti@1.9.4", "", {}, "sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw=="],
- "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+ "chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="],
- "get-tsconfig": ["get-tsconfig@4.14.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A=="],
+ "chartjs-chart-wordcloud": ["chartjs-chart-wordcloud@4.4.5", "", { "dependencies": { "@types/d3-cloud": "^1.2.9", "d3-cloud": "^1.2.7" }, "peerDependencies": { "chart.js": "^4.1.0" } }, "sha512-x7gdE5BZyj31+bjHbZ/0tX4hB6un7TQhvwdO5qHhqpmJCS6bONHjMuDzSjL/Qw2uI6CT2/U04LGqBREHwBiK3g=="],
- "hono": ["hono@4.13.0", "", {}, "sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ=="],
+ "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
- "postgres": ["postgres@3.4.9", "", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="],
+ "codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw=="],
- "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
+ "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
- "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+ "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
- "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
+ "compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="],
- "tsx": ["tsx@4.23.9", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-6q8uTORRGauQVjqMQnKUucLFoeXZAfw6zKvG35GLbdKWbLdeOtZ3H4mhyA5mxuUd2o2cRTskhj59nLLQseUvUw=="],
+ "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="],
+
+ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
+
+ "copy-text-to-clipboard": ["copy-text-to-clipboard@3.2.2", "", {}, "sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A=="],
+
+ "core-js": ["core-js@3.50.0", "", {}, "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw=="],
+
+ "core-js-compat": ["core-js-compat@3.50.0", "", { "dependencies": { "browserslist": "^4.28.7" } }, "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q=="],
+
+ "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="],
+
+ "crelt": ["crelt@1.0.7", "", {}, "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA=="],
+
+ "css-render": ["css-render@0.15.14", "", { "dependencies": { "@emotion/hash": "~0.8.0", "csstype": "~3.0.5" } }, "sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg=="],
+
+ "cssfilter": ["cssfilter@0.0.10", "", {}, "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="],
+
+ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
+
+ "cytoscape": ["cytoscape@3.34.0", "", {}, "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg=="],
+
+ "cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="],
+
+ "cytoscape-fcose": ["cytoscape-fcose@2.2.0", "", { "dependencies": { "cose-base": "^2.2.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ=="],
+
+ "d": ["d@1.0.2", "", { "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" } }, "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="],
+
+ "d3": ["d3@7.9.0", "", { "dependencies": { "d3-array": "3", "d3-axis": "3", "d3-brush": "3", "d3-chord": "3", "d3-color": "3", "d3-contour": "4", "d3-delaunay": "6", "d3-dispatch": "3", "d3-drag": "3", "d3-dsv": "3", "d3-ease": "3", "d3-fetch": "3", "d3-force": "3", "d3-format": "3", "d3-geo": "3", "d3-hierarchy": "3", "d3-interpolate": "3", "d3-path": "3", "d3-polygon": "3", "d3-quadtree": "3", "d3-random": "3", "d3-scale": "4", "d3-scale-chromatic": "3", "d3-selection": "3", "d3-shape": "3", "d3-time": "3", "d3-time-format": "4", "d3-timer": "3", "d3-transition": "3", "d3-zoom": "3" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="],
+
+ "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
+
+ "d3-axis": ["d3-axis@3.0.0", "", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="],
+
+ "d3-brush": ["d3-brush@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="],
+
+ "d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="],
+
+ "d3-cloud": ["d3-cloud@1.2.9", "", { "dependencies": { "d3-dispatch": "^1.0.3" } }, "sha512-leL1GLneC9ZQtnV+6TGWrNlGfI1WX7S2arcTv2vae12DaXo5wjm6GBCkskXbrDlyOymd/A75Pyj1H37MW4BZ/Q=="],
+
+ "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
+
+ "d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="],
+
+ "d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="],
+
+ "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="],
+
+ "d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="],
+
+ "d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="],
+
+ "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="],
+
+ "d3-fetch": ["d3-fetch@3.0.1", "", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="],
+
+ "d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="],
+
+ "d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="],
+
+ "d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="],
+
+ "d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="],
+
+ "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
+
+ "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
+
+ "d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="],
+
+ "d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="],
+
+ "d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="],
+
+ "d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="],
+
+ "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
+
+ "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="],
+
+ "d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="],
+
+ "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
+
+ "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
+
+ "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
+
+ "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
+
+ "d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="],
+
+ "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="],
+
+ "dagre-d3-es": ["dagre-d3-es@7.0.14", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg=="],
+
+ "date-fns": ["date-fns@4.4.0", "", {}, "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w=="],
+
+ "date-fns-tz": ["date-fns-tz@3.2.0", "", { "peerDependencies": { "date-fns": "^3.0.0 || ^4.0.0" } }, "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ=="],
+
+ "dayjs": ["dayjs@1.11.21", "", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="],
+
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+ "delaunator": ["delaunator@5.1.0", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ=="],
+
+ "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
+
+ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
+
+ "dom7": ["dom7@4.0.6", "", { "dependencies": { "ssr-window": "^4.0.0" } }, "sha512-emjdpPLhpNubapLFdjNL9tP06Sr+GZkrIHEXLWvOGsytACUrkbeIdjO5g77m00BrHTznnlcNqgmn7pCN192TBA=="],
+
+ "dompurify": ["dompurify@3.4.13", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ=="],
+
+ "drizzle-kit": ["drizzle-kit@0.31.10", "https://registry.npmjs.com/drizzle-kit/-/drizzle-kit-0.31.10.tgz", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="],
+
+ "drizzle-orm": ["drizzle-orm@0.45.2", "https://registry.npmjs.com/drizzle-orm/-/drizzle-orm-0.45.2.tgz", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="],
+
+ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
+
+ "electron-to-chromium": ["electron-to-chromium@1.5.402", "", {}, "sha512-/oOpMaPT6Yg+6/1XQhyIPlzgj7Ye9zf+nNM2Uh6OcE2G2oNptWazFa+qB2Pdqqbsc9KnIDzgAntoYN0dbwOXwA=="],
+
+ "elkjs": ["elkjs@0.8.2", "", {}, "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ=="],
+
+ "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
+
+ "err-code": ["err-code@3.0.1", "", {}, "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA=="],
+
+ "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
+
+ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
+
+ "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="],
+
+ "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
+
+ "es-toolkit": ["es-toolkit@1.50.0", "", {}, "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w=="],
+
+ "es5-ext": ["es5-ext@0.10.64", "", { "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg=="],
+
+ "es6-iterator": ["es6-iterator@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.35", "es6-symbol": "^3.1.1" } }, "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g=="],
+
+ "es6-symbol": ["es6-symbol@3.1.4", "", { "dependencies": { "d": "^1.0.2", "ext": "^1.7.0" } }, "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg=="],
+
+ "esbuild": ["esbuild@0.25.12", "https://registry.npmjs.com/esbuild/-/esbuild-0.25.12.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
+
+ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
+
+ "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
+
+ "esniff": ["esniff@2.0.1", "", { "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", "event-emitter": "^0.3.5", "type": "^2.7.2" } }, "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg=="],
+
+ "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
+
+ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
+
+ "event-emitter": ["event-emitter@0.3.5", "", { "dependencies": { "d": "1", "es5-ext": "~0.10.14" } }, "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="],
+
+ "evtd": ["evtd@0.2.4", "", {}, "sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw=="],
+
+ "exsolve": ["exsolve@1.1.1", "", {}, "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g=="],
+
+ "ext": ["ext@1.7.0", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="],
+
+ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+
+ "fflate": ["fflate@0.8.3", "", {}, "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="],
+
+ "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="],
+
+ "form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="],
+
+ "fsevents": ["fsevents@2.3.3", "https://registry.npmjs.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+
+ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
+
+ "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
+
+ "get-browser-rtc": ["get-browser-rtc@1.1.0", "", {}, "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ=="],
+
+ "get-intrinsic": ["get-intrinsic@1.3.0", "", { "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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
+
+ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
+
+ "get-tsconfig": ["get-tsconfig@4.14.1", "https://registry.npmjs.com/get-tsconfig/-/get-tsconfig-4.14.1.tgz", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A=="],
+
+ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
+
+ "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="],
+
+ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
+
+ "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
+
+ "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
+
+ "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="],
+
+ "hono": ["hono@4.13.0", "https://registry.npmjs.com/hono/-/hono-4.13.0.tgz", {}, "sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ=="],
+
+ "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
+
+ "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
+
+ "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
+
+ "i18next": ["i18next@23.16.8", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg=="],
+
+ "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
+
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+
+ "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="],
+
+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
+
+ "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
+
+ "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="],
+
+ "is-hotkey": ["is-hotkey@0.2.0", "", {}, "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw=="],
+
+ "is-network-error": ["is-network-error@1.3.2", "", {}, "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA=="],
+
+ "is-url": ["is-url@1.2.4", "", {}, "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="],
+
+ "isomorphic.js": ["isomorphic.js@0.2.5", "", {}, "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw=="],
+
+ "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
+
+ "jsbi": ["jsbi@3.2.5", "", {}, "sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ=="],
+
+ "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
+
+ "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
+
+ "katex": ["katex@0.16.47", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg=="],
+
+ "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="],
+
+ "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="],
+
+ "lib0": ["lib0@0.2.117", "", { "dependencies": { "isomorphic.js": "^0.2.4" }, "bin": { "0serve": "bin/0serve.js", "0gentesthtml": "bin/gentesthtml.js", "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js" } }, "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw=="],
+
+ "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="],
+
+ "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="],
+
+ "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="],
+
+ "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="],
+
+ "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="],
+
+ "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="],
+
+ "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="],
+
+ "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="],
+
+ "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="],
+
+ "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="],
+
+ "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="],
+
+ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="],
+
+ "linkify-it": ["linkify-it@5.0.2", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q=="],
+
+ "local-pkg": ["local-pkg@1.2.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q=="],
+
+ "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
+
+ "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="],
+
+ "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="],
+
+ "lodash.clonedeep": ["lodash.clonedeep@4.5.0", "", {}, "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="],
+
+ "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
+
+ "lodash.foreach": ["lodash.foreach@4.5.0", "", {}, "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ=="],
+
+ "lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="],
+
+ "lodash.toarray": ["lodash.toarray@4.4.0", "", {}, "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw=="],
+
+ "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
+
+ "magic-string": ["magic-string@1.1.0", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-kS3VHe0nEPST2saQV4Rbkchcd3UBRkVTQHo1D3h/ZTwFDhai/mfKkmtPAtD129EOI7K3HlHIsFOt0WrI2/oU9g=="],
+
+ "magic-string-ast": ["magic-string-ast@1.0.3", "", { "dependencies": { "magic-string": "^0.30.19" } }, "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA=="],
+
+ "markdown-it": ["markdown-it@14.3.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.5.0", "linkify-it": "^5.0.2", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw=="],
+
+ "markdown-it-image-figures": ["markdown-it-image-figures@2.1.1", "", { "peerDependencies": { "markdown-it": "*" } }, "sha512-mwXSQ2nPeVUzCMIE3HlLvjRioopiqyJLNph0pyx38yf9mpqFDhNGnMpAXF9/A2Xv0oiF2cVyg9xwfF0HNAz05g=="],
+
+ "markdown-it-sub": ["markdown-it-sub@2.0.0", "", {}, "sha512-iCBKgwCkfQBRg2vApy9vx1C1Tu6D8XYo8NvevI3OlwzBRmiMtsJ2sXupBgEA7PPxiDwNni3qIUkhZ6j5wofDUA=="],
+
+ "markdown-it-sup": ["markdown-it-sup@2.0.0", "", {}, "sha512-5VgmdKlkBd8sgXuoDoxMpiU+BiEt3I49GItBzzw7Mxq9CxvnhE/k09HFli09zgfFDRixDQDfDxi0mgBCXtaTvA=="],
+
+ "marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="],
+
+ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
+
+ "md-editor-v3": ["md-editor-v3@6.5.6", "", { "dependencies": { "@codemirror/autocomplete": "^6.18.7", "@codemirror/commands": "^6.8.1", "@codemirror/lang-markdown": "^6.3.4", "@codemirror/language": "^6.11.3", "@codemirror/language-data": "^6.5.1", "@codemirror/search": "^6.5.11", "@codemirror/state": "^6.5.2", "@codemirror/view": "^6.38.2", "@lezer/highlight": "^1.2.1", "@lucide/vue": "^1.16.0", "@types/markdown-it": "^14.0.1", "@vavt/copy2clipboard": "^1.0.1", "@vavt/markdown-theme": "^5.0.2", "@vavt/util": "^2.1.2", "codemirror": "^6.0.2", "markdown-it": "^14.0.0", "markdown-it-image-figures": "^2.1.1", "markdown-it-sub": "^2.0.0", "markdown-it-sup": "^2.0.0", "medium-zoom": "^1.1.0", "xss": "^1.0.15" }, "peerDependencies": { "vue": "^3.5.3" } }, "sha512-bD+xKTpaC9mGDE7W+9x25Y5mEsv21Y735J2Q0dxV+lWQTXH21/sKNdwaUYQ5dhXU2fl+oqrOJsEjSd58FLyNSA=="],
+
+ "mdurl": ["mdurl@2.1.0", "", {}, "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg=="],
+
+ "medium-zoom": ["medium-zoom@1.1.0", "", {}, "sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ=="],
+
+ "meow": ["meow@13.2.0", "", {}, "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA=="],
+
+ "mermaid": ["mermaid@11.16.1", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.2", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.2.0", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.20", "dompurify": "^3.3.3", "es-toolkit": "^1.45.1", "katex": "^0.16.45", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "sha512-TQsq6u22fAn3rek5VOubrhKPo1g5hwC3FXUN9hiyupTckcYiGuuKGkNQrKYwGJkXUxZdojwRG46gsSCFZMDp4g=="],
+
+ "mermaid-legacy": ["mermaid@9.4.3", "", { "dependencies": { "@braintree/sanitize-url": "^6.0.0", "cytoscape": "^3.23.0", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.1.0", "d3": "^7.4.0", "dagre-d3-es": "7.0.9", "dayjs": "^1.11.7", "dompurify": "2.4.3", "elkjs": "^0.8.2", "khroma": "^2.0.0", "lodash-es": "^4.17.21", "non-layered-tidy-tree-layout": "^2.0.2", "stylis": "^4.1.2", "ts-dedent": "^2.2.0", "uuid": "^9.0.0", "web-worker": "^1.2.0" } }, "sha512-TLkQEtqhRSuEHSE34lh5bCa94KATCyluAXmFnNI2PRZwOpXFeqiJWwZl+d2CcemE1RS6QbbueSSq9QIg8Uxcyw=="],
+
+ "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
+
+ "mime-match": ["mime-match@1.0.2", "", { "dependencies": { "wildcard": "^1.1.0" } }, "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg=="],
+
+ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
+
+ "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="],
+
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+ "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
+
+ "naive-ui": ["naive-ui@2.44.1", "", { "dependencies": { "@css-render/plugin-bem": "^0.15.14", "@css-render/vue3-ssr": "^0.15.14", "@types/lodash": "^4.17.20", "@types/lodash-es": "^4.17.12", "async-validator": "^4.2.5", "css-render": "^0.15.14", "csstype": "^3.1.3", "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", "evtd": "^0.2.4", "highlight.js": "^11.8.0", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "seemly": "^0.3.10", "treemate": "^0.3.11", "vdirs": "^0.1.8", "vooks": "^0.2.12", "vueuc": "^0.4.65" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-reo8Esw0p58liZwbUutC7meW24Xbn3EwNv91zReWKm2W4JPu+zfgJRn/F7aO0BFmvN+h2brA2M5lRvYqLq4kuA=="],
+
+ "namespace-emitter": ["namespace-emitter@2.0.1", "", {}, "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g=="],
+
+ "nanoid": ["nanoid@6.0.1", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-3wVS3i51pE2pi1k5FFL/95BGfVS0kSsvDVuGXHOtxox/TywUmtgq+3qiTOTbs9J7KfHaXPiN171k/A6dBnaXFw=="],
+
+ "next-tick": ["next-tick@1.1.0", "", {}, "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="],
+
+ "node-releases": ["node-releases@2.0.53", "", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="],
+
+ "non-layered-tidy-tree-layout": ["non-layered-tidy-tree-layout@2.0.2", "", {}, "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="],
+
+ "normalize.css": ["normalize.css@8.0.1", "", {}, "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="],
+
+ "nostics": ["nostics@1.2.0", "", {}, "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg=="],
+
+ "obug": ["obug@2.1.4", "", {}, "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA=="],
+
+ "p-retry": ["p-retry@6.2.1", "", { "dependencies": { "@types/retry": "0.12.2", "is-network-error": "^1.0.0", "retry": "^0.13.1" } }, "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ=="],
+
+ "package-manager-detector": ["package-manager-detector@1.8.0", "", {}, "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A=="],
+
+ "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="],
+
+ "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
+
+ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+
+ "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="],
+
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+ "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
+
+ "pinia": ["pinia@4.0.2", "", { "dependencies": { "nostics": "^1.1.4" }, "peerDependencies": { "@vue/devtools-api": "^8.1.5", "typescript": ">=5.6.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-yKVVA7bSj5oRZFp/Ab9wLlmyb5gPUYEiIm4ryiWTe/xe7PtkRdMVOp1X1ggvq0c6Uj7Q0Du1HnV2mtAwM0Ks1g=="],
+
+ "pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "^0.2.4", "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="],
+
+ "points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="],
+
+ "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="],
+
+ "postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="],
+
+ "postgres": ["postgres@3.4.9", "https://registry.npmjs.com/postgres/-/postgres-3.4.9.tgz", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="],
+
+ "preact": ["preact@10.29.8", "", { "peerDependencies": { "preact-render-to-string": ">=5" }, "optionalPeers": ["preact-render-to-string"] }, "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q=="],
+
+ "prettier": ["prettier@3.9.6", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="],
+
+ "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
+
+ "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="],
+
+ "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
+
+ "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
+
+ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
+
+ "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="],
+
+ "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
+
+ "readdirp": ["readdirp@5.1.1", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="],
+
+ "regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="],
+
+ "regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="],
+
+ "regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="],
+
+ "regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="],
+
+ "regjsgen": ["regjsgen@0.8.0", "", {}, "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q=="],
+
+ "regjsparser": ["regjsparser@0.13.2", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ=="],
+
+ "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
+
+ "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "https://registry.npmjs.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
+
+ "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
+
+ "robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="],
+
+ "rolldown": ["rolldown@1.2.3", "", { "dependencies": { "@oxc-project/types": "=0.143.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.3", "@rolldown/binding-darwin-arm64": "1.2.3", "@rolldown/binding-darwin-x64": "1.2.3", "@rolldown/binding-freebsd-x64": "1.2.3", "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", "@rolldown/binding-linux-arm64-gnu": "1.2.3", "@rolldown/binding-linux-arm64-musl": "1.2.3", "@rolldown/binding-linux-ppc64-gnu": "1.2.3", "@rolldown/binding-linux-s390x-gnu": "1.2.3", "@rolldown/binding-linux-x64-gnu": "1.2.3", "@rolldown/binding-linux-x64-musl": "1.2.3", "@rolldown/binding-openharmony-arm64": "1.2.3", "@rolldown/binding-win32-arm64-msvc": "1.2.3", "@rolldown/binding-win32-x64-msvc": "1.2.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A=="],
+
+ "roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="],
+
+ "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="],
+
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
+
+ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
+
+ "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="],
+
+ "scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
+
+ "seemly": ["seemly@0.3.10", "", {}, "sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q=="],
+
+ "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+ "simple-peer": ["simple-peer@9.11.1", "", { "dependencies": { "buffer": "^6.0.3", "debug": "^4.3.2", "err-code": "^3.0.1", "get-browser-rtc": "^1.1.0", "queue-microtask": "^1.2.3", "randombytes": "^2.1.0", "readable-stream": "^3.6.0" } }, "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw=="],
+
+ "skulpt": ["skulpt@1.2.0", "", { "dependencies": { "jsbi": "^3.1.4" } }, "sha512-T0cv0sdSOXLlIJTuyXSeYJ3TFdWYSZfX2PcBLpRKrKZ3dTbVQXRMiYudSuko2xzcyMif47DS2ShatCwjCbsSsA=="],
+
+ "slate": ["slate@0.124.1", "", {}, "sha512-ii7DwezgvbLAyKtHBIunjTR1kzbNfYLCUKLMzJELlbTZkvHzX4DzN7HKIwcakf6dPxO6AoeT/P7kHOcyTym/hA=="],
+
+ "slate-history": ["slate-history@0.115.0", "", { "peerDependencies": { "slate": ">=0.114.3" } }, "sha512-QdUm9aVyQFz6JG4a84Z6Um+tJpZJmsh9bjXwTVTvYiN4rdKbqL6+/4HT94on1WYxe10Q4vY6mA6BCpoYxgF3tQ=="],
+
+ "snabbdom": ["snabbdom@3.6.4", "", {}, "sha512-VmxEfuw1/Y/eFj5VtMhYnukExpYiPkNzoo3+N3qwAOUDMl8wXgbli5ebR+j0knE3lZ/0eYskLxNcX64uy10N9w=="],
+
+ "source-map": ["source-map@0.6.1", "https://registry.npmjs.com/source-map/-/source-map-0.6.1.tgz", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
+ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
+
+ "source-map-support": ["source-map-support@0.5.21", "https://registry.npmjs.com/source-map-support/-/source-map-support-0.5.21.tgz", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
+
+ "ssr-window": ["ssr-window@4.0.2", "", {}, "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ=="],
+
+ "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
+
+ "strip-literal": ["strip-literal@4.0.0", "", { "dependencies": { "js-tokens": "^10.0.0" } }, "sha512-PaqAvfUZKBwc/SLmNZtHmzK+v19Z4O4eS3cKPeGvbIv/U3pnyEq4Tuw3/4v/FwfM8VQaEawsyCcOQ0P+kpwWWw=="],
+
+ "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="],
+
+ "stylis": ["stylis@4.4.0", "", {}, "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA=="],
+
+ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
+
+ "systemjs": ["systemjs@6.15.1", "", {}, "sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA=="],
+
+ "terser": ["terser@5.49.2", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-rGbJiKeQ4WDe3EXlDAIaQcwftVfv2Q8o1awFNfvXolJYKkb1AuZY1RTOmqx4LJXZENbWZA7eIsYGHuEzHsi1nQ=="],
+
+ "tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="],
+
+ "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
+
+ "treemate": ["treemate@0.3.11", "", {}, "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg=="],
+
+ "ts-dedent": ["ts-dedent@2.3.0", "", {}, "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg=="],
+
+ "tsx": ["tsx@4.23.9", "https://registry.npmjs.com/tsx/-/tsx-4.23.9.tgz", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-6q8uTORRGauQVjqMQnKUucLFoeXZAfw6zKvG35GLbdKWbLdeOtZ3H4mhyA5mxuUd2o2cRTskhj59nLLQseUvUw=="],
+
+ "type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="],
"typescript": ["typescript@5.9.3", "https://registry.npmjs.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
+ "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
+
+ "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="],
+
"undici-types": ["undici-types@8.3.0", "https://registry.npmjs.com/undici-types/-/undici-types-8.3.0.tgz", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
+ "unicode-canonical-property-names-ecmascript": ["unicode-canonical-property-names-ecmascript@2.0.1", "", {}, "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg=="],
+
+ "unicode-match-property-ecmascript": ["unicode-match-property-ecmascript@2.0.0", "", { "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" } }, "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q=="],
+
+ "unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="],
+
+ "unicode-property-aliases-ecmascript": ["unicode-property-aliases-ecmascript@2.2.0", "", {}, "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ=="],
+
+ "unimport": ["unimport@6.4.0", "", { "dependencies": { "acorn": "^8.18.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "local-pkg": "^1.2.1", "magic-string": "^1.1.0", "mlly": "^1.8.2", "pathe": "^2.0.3", "picomatch": "^4.0.5", "pkg-types": "^2.3.1", "scule": "^1.3.0", "strip-literal": "^4.0.0", "tinyglobby": "^0.2.17", "unplugin": "^3.3.0", "unplugin-utils": "^0.3.2" }, "peerDependencies": { "oxc-parser": "*", "rolldown": "^1.0.0" }, "optionalPeers": ["oxc-parser", "rolldown"] }, "sha512-JJOOuNMFq8b4ZPBKwQUxEcba4MplskDzYI1Lvrf8rJfWphZTWvPNXWa493qsPngHUmub89w6C7j+SeLWTE/UIQ=="],
+
+ "unplugin": ["unplugin@3.3.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.4", "webpack-virtual-modules": "^0.6.2" }, "peerDependencies": { "@farmfe/core": "*", "@rspack/core": "*", "bun-types-no-globals": "*", "esbuild": "*", "rolldown": "*", "rollup": "*", "unloader": "*", "vite": "*", "webpack": "*" }, "optionalPeers": ["@farmfe/core", "@rspack/core", "bun-types-no-globals", "esbuild", "rolldown", "rollup", "unloader", "vite", "webpack"] }, "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg=="],
+
+ "unplugin-auto-import": ["unplugin-auto-import@21.1.0", "", { "dependencies": { "local-pkg": "^1.2.1", "magic-string": "^1.1.0", "picomatch": "^4.0.5", "unimport": "^6.4.0", "unplugin": "^3.3.0", "unplugin-utils": "^0.3.2" }, "peerDependencies": { "@nuxt/kit": "^4.0.0", "@vueuse/core": "*" }, "optionalPeers": ["@nuxt/kit", "@vueuse/core"] }, "sha512-EzrSqWIBulEqCuP7idADXH+tVKYrbwKlR5+r/lOWik0o+Ksny1kitmtryHGNSv7puzzORlcIwT/x2JStiszlHA=="],
+
+ "unplugin-utils": ["unplugin-utils@0.3.2", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.4" } }, "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA=="],
+
+ "unplugin-vue-components": ["unplugin-vue-components@32.1.0", "", { "dependencies": { "chokidar": "^5.0.0", "local-pkg": "^1.2.0", "magic-string": "^0.30.21", "mlly": "^1.8.2", "obug": "^2.1.1", "picomatch": "^4.0.4", "tinyglobby": "^0.2.16", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "@nuxt/kit": "^3.2.2 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@nuxt/kit"] }, "sha512-YiUkSxuRjab18XFOrX5VsIxXzccrfmHVGsGeJgSgklb829DQmCy9E4vvDUE4tuvZZdxyFJZX0Oc4TPnnxiiMyg=="],
+
+ "update-browserslist-db": ["update-browserslist-db@1.3.0", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA=="],
+
+ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
+
+ "uuid": ["uuid@14.0.1", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="],
+
+ "vdirs": ["vdirs@0.1.8", "", { "dependencies": { "evtd": "^0.2.2" }, "peerDependencies": { "vue": "^3.0.11" } }, "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw=="],
+
+ "vite": ["vite@8.2.1", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.25", "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.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" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw=="],
+
+ "vooks": ["vooks@0.2.12", "", { "dependencies": { "evtd": "^0.2.2" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q=="],
+
+ "vue": ["vue@3.5.41", "", { "dependencies": { "@vue/compiler-dom": "3.5.41", "@vue/compiler-sfc": "3.5.41", "@vue/runtime-dom": "3.5.41", "@vue/server-renderer": "3.5.41", "@vue/shared": "3.5.41" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg=="],
+
+ "vue-chartjs": ["vue-chartjs@5.3.4", "", { "peerDependencies": { "chart.js": "^4.1.1", "vue": "^3.0.0-0 || ^2.7.0" } }, "sha512-x3Fqob8RQvrTdssfi9ecsCzEkFOd8JPmNwSkSQzdfKj/uBsRJs/Y88cZcZIEcPsTVfMGwMo4MOoihoDG2DoE/g=="],
+
+ "vue-codemirror": ["vue-codemirror@6.1.1", "", { "dependencies": { "@codemirror/commands": "6.x", "@codemirror/language": "6.x", "@codemirror/state": "6.x", "@codemirror/view": "6.x" }, "peerDependencies": { "codemirror": "6.x", "vue": "3.x" } }, "sha512-rTAYo44owd282yVxKtJtnOi7ERAcXTeviwoPXjIc6K/IQYUsoDkzPvw/JDFtSP6T7Cz/2g3EHaEyeyaQCKoDMg=="],
+
+ "vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="],
+
+ "vue-router": ["vue-router@5.2.0", "", { "dependencies": { "@babel/generator": "^8.0.0", "@vue-macros/common": "^3.1.3", "@vue/devtools-api": "^8.1.5", "ast-walker-scope": "^0.9.0", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.2.1", "magic-string": "^0.30.21", "mlly": "^1.8.2", "muggle-string": "^0.4.1", "nostics": "^1.1.4", "pathe": "^2.0.3", "picomatch": "^4.0.5", "scule": "^1.3.0", "tinyglobby": "^0.2.17", "unplugin": "^3.3.0", "unplugin-utils": "^0.3.2", "yaml": "^2.9.0" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.34 || ^4.0.0", "pinia": "^3.0.4 || ^4.0.2", "vite": "^7.3.0 || ^8.0.0", "vue": "^3.5.34 || ^4.0.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia", "vite"] }, "sha512-QAC5i0LEb1GLG0LXDQmHu8L7FX12j0KwU/JTKmLQUJMrn04gQdKP6Du+p0QwpHb3iy71vBlqnHQ8WAfOSAWhqw=="],
+
+ "vueuc": ["vueuc@0.4.65", "", { "dependencies": { "@css-render/vue3-ssr": "^0.15.10", "@juggle/resize-observer": "^3.3.1", "css-render": "^0.15.10", "evtd": "^0.2.4", "seemly": "^0.3.6", "vdirs": "^0.1.4", "vooks": "^0.2.4" }, "peerDependencies": { "vue": "^3.0.11" } }, "sha512-lXuMl+8gsBmruudfxnMF9HW4be8rFziylXFu1VHVNbLVhRTXXV4njvpRuJapD/8q+oFEMSfQMH16E/85VoWRyQ=="],
+
+ "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
+
+ "web-worker": ["web-worker@1.5.0", "", {}, "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw=="],
+
+ "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
+
+ "wildcard": ["wildcard@1.1.2", "", {}, "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng=="],
+
+ "ws": ["ws@8.21.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-54dMVAo4WIe6SKy3vBgN+9bJZqqQ8IMRevAkOLQALhi49qkkQDQfWdAZ8KQlXiEabw88ARXXdUrlvtbKQX+aKw=="],
+
+ "xss": ["xss@1.0.15", "", { "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" }, "bin": { "xss": "bin/xss" } }, "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg=="],
+
+ "y-codemirror.next": ["y-codemirror.next@0.3.5", "", { "dependencies": { "lib0": "^0.2.42" }, "peerDependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "yjs": "^13.5.6" } }, "sha512-VluNu3e5HfEXybnypnsGwKAj+fKLd4iAnR7JuX1Sfyydmn1jCBS5wwEL/uS04Ch2ib0DnMAOF6ZRR/8kK3wyGw=="],
+
+ "y-protocols": ["y-protocols@1.0.7", "", { "dependencies": { "lib0": "^0.2.85" }, "peerDependencies": { "yjs": "^13.0.0" } }, "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw=="],
+
+ "y-webrtc": ["y-webrtc@10.3.0", "", { "dependencies": { "lib0": "^0.2.42", "simple-peer": "^9.11.0", "y-protocols": "^1.0.6" }, "optionalDependencies": { "ws": "^8.14.2" }, "peerDependencies": { "yjs": "^13.6.8" }, "bin": { "y-webrtc-signaling": "bin/server.js" } }, "sha512-KalJr7dCgUgyVFxoG3CQYbpS0O2qybegD0vI4bYnYHI0MOwoVbucED3RZ5f2o1a5HZb1qEssUKS0H/Upc6p1lA=="],
+
+ "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
+
+ "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
+
+ "yjs": ["yjs@13.6.32", "", { "dependencies": { "lib0": "^0.2.99" } }, "sha512-lfiJIIC4Xayt5ItynE407ehlE03pCjeOc4hkR4yxxvvNJ4kuiN25B0g+Qp8XagYz361LLL7DCzR5bvFJ81QKtQ=="],
+
"zod": ["zod@4.4.3", "https://registry.npmjs.com/zod/-/zod-4.4.3.tgz", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
- "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
+ "@babel/core/@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="],
- "tsx/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
+ "@babel/generator/@babel/parser": ["@babel/parser@8.0.4", "", { "dependencies": { "@babel/types": "^8.0.4" }, "bin": "./bin/babel-parser.js" }, "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
+ "@babel/generator/@babel/types": ["@babel/types@8.0.4", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.4" } }, "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
+ "@babel/traverse/@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="],
+ "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "https://registry.npmjs.com/esbuild/-/esbuild-0.18.20.tgz", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="],
+ "@oj2/web/typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="],
+ "@types/d3-cloud/@types/d3": ["@types/d3@3.5.53", "", {}, "sha512-8yKQA9cAS6+wGsJpBysmnhlaaxlN42Qizqkw+h2nILSlS+MAG2z4JdO6p+PJrJ+ACvimkmLJL281h157e52psQ=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="],
+ "@uppy/core/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="],
+ "@vue-flow/core/@vueuse/core": ["@vueuse/core@10.11.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "10.11.1", "@vueuse/shared": "10.11.1", "vue-demi": ">=0.14.8" } }, "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="],
+ "@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="],
+ "@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="],
+ "@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="],
+ "@vue/compiler-sfc/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="],
+ "@wangeditor-next/basic-modules/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="],
+ "@wangeditor-next/core/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="],
+ "@wangeditor-next/editor/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="],
+ "@wangeditor-next/table-module/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="],
+ "@wangeditor-next/video-module/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="],
+ "css-render/csstype": ["csstype@3.0.11", "", {}, "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="],
+ "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="],
+ "d3-cloud/d3-dispatch": ["d3-dispatch@1.0.6", "", {}, "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="],
+ "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="],
+ "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="],
- "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
+ "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="],
- "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
+ "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
- "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
+ "magic-string-ast/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
- "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="],
+ "mermaid-legacy/@braintree/sanitize-url": ["@braintree/sanitize-url@6.0.4", "", {}, "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A=="],
- "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="],
+ "mermaid-legacy/dagre-d3-es": ["dagre-d3-es@7.0.9", "", { "dependencies": { "d3": "^7.8.2", "lodash-es": "^4.17.21" } }, "sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w=="],
- "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="],
+ "mermaid-legacy/dompurify": ["dompurify@2.4.3", "", {}, "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="],
- "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="],
+ "mermaid-legacy/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
- "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="],
+ "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
- "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="],
+ "postcss/nanoid": ["nanoid@3.3.17", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g=="],
- "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="],
+ "strip-literal/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="],
- "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="],
+ "tsx/esbuild": ["esbuild@0.28.1", "https://registry.npmjs.com/esbuild/-/esbuild-0.28.1.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
- "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="],
+ "unplugin-vue-components/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
- "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="],
+ "vue-router/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
- "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="],
+ "@babel/generator/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="],
- "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="],
+ "@babel/generator/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.4", "", {}, "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg=="],
- "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="],
+ "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "https://registry.npmjs.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
- "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="],
+ "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "https://registry.npmjs.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
- "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="],
+ "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "https://registry.npmjs.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="],
- "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="],
+ "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "https://registry.npmjs.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="],
- "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="],
+ "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "https://registry.npmjs.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="],
- "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="],
+ "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "https://registry.npmjs.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="],
- "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="],
+ "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "https://registry.npmjs.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="],
- "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="],
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "https://registry.npmjs.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="],
- "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="],
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "https://registry.npmjs.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="],
- "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="],
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "https://registry.npmjs.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="],
- "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="],
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "https://registry.npmjs.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="],
- "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "https://registry.npmjs.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "https://registry.npmjs.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "https://registry.npmjs.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "https://registry.npmjs.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "https://registry.npmjs.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "https://registry.npmjs.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "https://registry.npmjs.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "https://registry.npmjs.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "https://registry.npmjs.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "https://registry.npmjs.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "https://registry.npmjs.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
+
+ "@vue-flow/core/@vueuse/core/@types/web-bluetooth": ["@types/web-bluetooth@0.0.20", "", {}, "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="],
+
+ "@vue-flow/core/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@10.11.1", "", {}, "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw=="],
+
+ "@vue-flow/core/@vueuse/core/@vueuse/shared": ["@vueuse/shared@10.11.1", "", { "dependencies": { "vue-demi": ">=0.14.8" } }, "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA=="],
+
+ "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="],
+
+ "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="],
+
+ "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="],
+
+ "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
+
+ "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "https://registry.npmjs.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
+
+ "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "https://registry.npmjs.com/@esbuild/android-arm/-/android-arm-0.28.1.tgz", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
+
+ "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="],
+
+ "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "https://registry.npmjs.com/@esbuild/android-x64/-/android-x64-0.28.1.tgz", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="],
+
+ "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="],
+
+ "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "https://registry.npmjs.com/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="],
+
+ "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="],
+
+ "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "https://registry.npmjs.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="],
+
+ "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "https://registry.npmjs.com/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="],
+
+ "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="],
+
+ "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "https://registry.npmjs.com/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="],
+
+ "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "https://registry.npmjs.com/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="],
+
+ "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "https://registry.npmjs.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="],
+
+ "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "https://registry.npmjs.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="],
+
+ "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "https://registry.npmjs.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="],
+
+ "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "https://registry.npmjs.com/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="],
+
+ "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "https://registry.npmjs.com/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="],
+
+ "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="],
+
+ "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "https://registry.npmjs.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="],
+
+ "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="],
+
+ "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "https://registry.npmjs.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="],
+
+ "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="],
+
+ "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "https://registry.npmjs.com/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="],
+
+ "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="],
+
+ "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "https://registry.npmjs.com/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="],
+
+ "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "https://registry.npmjs.com/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
}
}