diff --git a/.env b/.env new file mode 100644 index 0000000..6e2d297 --- /dev/null +++ b/.env @@ -0,0 +1,31 @@ +# ============================================================ +# 第九联盟AI坐席辅助系统 - Docker 部署环境变量 +# 复制本文件为 .env.local 并填入真实值,.env.local 不会被 Git 提交 +# ============================================================ + +# ---------------------- 数据库 ---------------------- +MYSQL_ROOT_PASSWORD=root +MYSQL_DATABASE=ai_assistant + +# ---------------------- Redis ---------------------- +REDIS_PASSWORD= + +# ---------------------- RabbitMQ ---------------------- +RABBITMQ_DEFAULT_USER=admin +RABBITMQ_DEFAULT_PASS=admin + +# ---------------------- 企微配置 ---------------------- +# 会话存档 Secret(企业微信管理后台获取) +WECOM_ARCHIVE_SECRET= +# RSA 私钥(PKCS#1 格式,用于解密会话存档) +WECOM_ARCHIVE_RSA_KEY= +# 回调验证 Token +WECOM_ARCHIVE_CALLBACK_TOKEN= +# SDK 路径(容器内路径,Linux部署时挂载 .so 文件) +WECOM_ARCHIVE_SDK_PATH=/app/sdk + +# ---------------------- 通义千问 API ---------------------- +DASHSCOPE_API_KEY= + +# ---------------------- JWT ---------------------- +JWT_SECRET=your-256-bit-secret-key-here-at-least-32-characters diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..77ceaf9 --- /dev/null +++ b/.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? +node_modules/ + +.planning/ +.vscode/ +deploy-package/ + diff --git a/.planning/STATE.md b/.planning/STATE.md index ac01075..6c00622 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -1,7 +1,7 @@ # 项目状态 ## 当前阶段 -Phase 6 完成(前端 + 管理后台 + 部署配置) +Phase 6 完成(前端 + 管理后台 + 部署配置),Plan A 实施完成 ## 已完成 - [x] 项目初始化(git repo、.planning目录、GSD规划文件) @@ -26,6 +26,20 @@ Phase 6 完成(前端 + 管理后台 + 部署配置) - kb-admin-service: 话术管理API - analytics-service: 数据统计API - 所有服务Dockerfile + nginx + 完整docker-compose编排 +- [x] Plan A: Admin后台真实API打通 + - kb-admin-service 新增 AuthController(JWT登录)、AdminController(管理员CRUD)、KnowledgeController(知识库CRUD+上传) + - 前端 request.ts Axios封装 + Token拦截 + 401跳转 + - 前端 Login.tsx 真实登录API + - 前端 UtteranceList.tsx 真实CRUD(分页字段兼容:list→records) + - 前端 KnowledgeList.tsx 知识库管理(列表/手动录入/上传/编辑/删除) + - 前端 AdminList.tsx 管理员账号管理(角色标签修复 SUPER_ADMIN→超管) + - 后端 AdminController 安全修复:返回前清除password字段 + - 前端 Layout.tsx 路由守卫 + 菜单更新 + +## 运行状态 +- 后端7服务全部启动:Gateway(8080), Auth(8081), Conversation(8083), Intent(8084), Recommendation(8085), Generation(8086), KbAdmin(8087) +- 前端dev server:http://localhost:5174 +- 数据库:MySQL 5.7 (localhost), Redis (Docker), RabbitMQ (Docker) ## 提交记录 - `31a395b` feat(01): Phase 1 - 基础设施 + 数据库 + 公共模块 @@ -38,6 +52,6 @@ Phase 6 完成(前端 + 管理后台 + 部署配置) 1. 配置通义千问API Key并测试LLM调用 2. 配置企微CorpID/Secret并测试OAuth登录 3. 上传企微C SDK动态库到archive-service -4. 补充前端API真实调用(当前为模拟数据) -5. 补充单元测试和E2E测试 -6. 配置CI/CD流水线 +4. 补充单元测试和E2E测试 +5. 配置CI/CD流水线 +6. 知识库PDF/DOCX文件解析实现 diff --git a/GenerateHash.class b/GenerateHash.class new file mode 100644 index 0000000..5f0f713 Binary files /dev/null and b/GenerateHash.class differ diff --git a/GenerateHash.java b/GenerateHash.java new file mode 100644 index 0000000..f3e2e21 --- /dev/null +++ b/GenerateHash.java @@ -0,0 +1,9 @@ +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +public class GenerateHash { + public static void main(String[] args) { + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + String hash = encoder.encode("admin123"); + System.out.println(hash); + System.out.println("验证: " + encoder.matches("admin123", hash)); + } +} diff --git a/backend/analytics-service/Dockerfile b/backend/analytics-service/Dockerfile index 858c6fb..ae59464 100644 --- a/backend/analytics-service/Dockerfile +++ b/backend/analytics-service/Dockerfile @@ -1,5 +1,5 @@ -FROM openjdk:17-jdk-slim +FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY target/*.jar app.jar -EXPOSE 8080 +EXPOSE 8088 ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/backend/analytics-service/src/main/java/com/artedu/analytics/controller/AnalyticsController.java b/backend/analytics-service/src/main/java/com/artedu/analytics/controller/AnalyticsController.java index e489f07..5dd2c8b 100644 --- a/backend/analytics-service/src/main/java/com/artedu/analytics/controller/AnalyticsController.java +++ b/backend/analytics-service/src/main/java/com/artedu/analytics/controller/AnalyticsController.java @@ -1,46 +1,50 @@ package com.artedu.analytics.controller; +import com.artedu.analytics.service.AnalyticsService; import com.artedu.common.result.Result; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import javax.validation.constraints.NotBlank; -import java.util.HashMap; import java.util.List; import java.util.Map; @Slf4j +@Validated @RestController @RequestMapping("/api/v1/analytics") public class AnalyticsController { + @Autowired + private AnalyticsService analyticsService; + @GetMapping("/dashboard") - public Result> dashboard(@RequestParam String corpId) { - Map result = new HashMap<>(); - result.put("todayConversations", 128); - result.put("todayMessages", 2456); - result.put("activeStaff", 24); - result.put("utteranceAdoptionRate", 0.685); - result.put("conversionRate", 0.123); - result.put("avgResponseTimeMs", 18500); + public Result> dashboard(@NotBlank(message = "corpId不能为空") @RequestParam String corpId) { + log.info("查询数据看板, corpId={}", corpId); + Map result = analyticsService.getDashboard(corpId); return Result.success(result); } @GetMapping("/utterance-performance") public Result>> utterancePerformance( - @RequestParam String corpId, + @NotBlank(message = "corpId不能为空") @RequestParam String corpId, @RequestParam(defaultValue = "7") Integer days) { - // TODO: 查询真实数据 - return Result.success(List.of()); + log.info("查询话术绩效, corpId={}, days={}", corpId, days); + List> result = analyticsService.getUtterancePerformance(corpId, days); + return Result.success(result); } @GetMapping("/staff-performance") public Result>> staffPerformance( - @RequestParam String corpId, + @NotBlank(message = "corpId不能为空") @RequestParam String corpId, @RequestParam(defaultValue = "7") Integer days) { - // TODO: 查询真实数据 - return Result.success(List.of()); + log.info("查询员工绩效, corpId={}, days={}", corpId, days); + List> result = analyticsService.getStaffPerformance(corpId, days); + return Result.success(result); } } diff --git a/backend/analytics-service/src/main/java/com/artedu/analytics/service/AnalyticsService.java b/backend/analytics-service/src/main/java/com/artedu/analytics/service/AnalyticsService.java new file mode 100644 index 0000000..647b192 --- /dev/null +++ b/backend/analytics-service/src/main/java/com/artedu/analytics/service/AnalyticsService.java @@ -0,0 +1,114 @@ +package com.artedu.analytics.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +@Slf4j +@Service +public class AnalyticsService { + + @Autowired + private JdbcTemplate jdbcTemplate; + + public Map getDashboard(String corpId) { + Map result = new HashMap<>(); + + String today = LocalDate.now().toString(); + String todayStart = today + " 00:00:00"; + String todayEnd = today + " 23:59:59"; + + // 1. 今日会话数 + Long todayConversations = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM conversations WHERE corp_id = ? AND start_time BETWEEN ? AND ?", + Long.class, corpId, todayStart, todayEnd); + result.put("todayConversations", todayConversations != null ? todayConversations : 0); + + // 2. 今日消息数(对话轮次总数) + Long todayMessages = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM conversation_turns ct " + + "JOIN conversations c ON ct.session_id = c.session_id " + + "WHERE c.corp_id = ? AND ct.created_at BETWEEN ? AND ?", + Long.class, corpId, todayStart, todayEnd); + result.put("todayMessages", todayMessages != null ? todayMessages : 0); + + // 3. 活跃坐席数(今日有会话的坐席) + Long activeStaff = jdbcTemplate.queryForObject( + "SELECT COUNT(DISTINCT staff_id) FROM conversations WHERE corp_id = ? AND start_time BETWEEN ? AND ?", + Long.class, corpId, todayStart, todayEnd); + result.put("activeStaff", activeStaff != null ? activeStaff : 0); + + // 4. 话术采纳率(有 staff_action 为 SENT 的推荐 / 总推荐数) + Double utteranceAdoptionRate = jdbcTemplate.queryForObject( + "SELECT IFNULL(SUM(CASE WHEN staff_action IN ('SENT', 'ACCEPTED') THEN 1 ELSE 0 END) * 1.0 / COUNT(*), 0) " + + "FROM recommendations WHERE corp_id = ?", + Double.class, corpId); + result.put("utteranceAdoptionRate", utteranceAdoptionRate != null ? utteranceAdoptionRate : 0.0); + + // 5. 转化率(feedback 为 POSITIVE 或 CONVERTED 的比例) + Double conversionRate = jdbcTemplate.queryForObject( + "SELECT IFNULL(SUM(CASE WHEN feedback IN ('POSITIVE', 'CONVERTED') THEN 1 ELSE 0 END) * 1.0 / COUNT(*), 0) " + + "FROM recommendations WHERE corp_id = ? AND staff_action IN ('SENT', 'ACCEPTED')", + Double.class, corpId); + result.put("conversionRate", conversionRate != null ? conversionRate : 0.0); + + // 6. 平均响应时间(recommendations 的 elapsed_ms 平均) + Double avgResponseTimeMs = jdbcTemplate.queryForObject( + "SELECT IFNULL(AVG(elapsed_ms), 0) FROM recommendations WHERE corp_id = ?", + Double.class, corpId); + result.put("avgResponseTimeMs", avgResponseTimeMs != null ? Math.round(avgResponseTimeMs) : 0); + + return result; + } + + public List> getUtterancePerformance(String corpId, int days) { + String startDate = LocalDate.now().minusDays(days).toString(); + + String sql = "SELECT " + + " u.utterance_id AS utteranceId, " + + " u.title, " + + " SUM(up.show_count) AS totalShow, " + + " SUM(up.click_count) AS totalClick, " + + " SUM(up.send_count) AS totalSend, " + + " SUM(up.positive_count) AS totalPositive, " + + " SUM(up.conversion_count) AS totalConversion, " + + " IFNULL(SUM(up.click_count) * 1.0 / SUM(up.show_count), 0) AS ctr, " + + " IFNULL(SUM(up.conversion_count) * 1.0 / NULLIF(SUM(up.send_count), 0), 0) AS conversionRate, " + + " IFNULL(AVG(up.score), 0) AS avgScore " + + "FROM utterances u " + + "LEFT JOIN utterance_performance up ON u.utterance_id = up.utterance_id AND up.corp_id = ? " + + "WHERE u.corp_id = ? OR u.corp_id = 'corp001' " + + "GROUP BY u.utterance_id, u.title " + + "ORDER BY totalSend DESC " + + "LIMIT 20"; + + return jdbcTemplate.queryForList(sql, corpId, corpId); + } + + public List> getStaffPerformance(String corpId, int days) { + String startDate = LocalDate.now().minusDays(days).toString(); + String startTime = startDate + " 00:00:00"; + + String sql = "SELECT " + + " c.staff_id AS staffId, " + + " MAX(c.staff_name) AS staffName, " + + " COUNT(DISTINCT c.session_id) AS conversationCount, " + + " SUM(c.round_count) AS totalRounds, " + + " SUM(c.intent_count) AS totalIntents, " + + " AVG(c.idle_duration) AS avgIdleDuration, " + + " COUNT(DISTINCT c.customer_id) AS uniqueCustomers, " + + " MAX(c.start_time) AS lastActiveTime " + + "FROM conversations c " + + "WHERE c.corp_id = ? AND c.start_time >= ? " + + "GROUP BY c.staff_id " + + "ORDER BY conversationCount DESC"; + + return jdbcTemplate.queryForList(sql, corpId, startTime); + } +} diff --git a/backend/analytics-service/src/main/resources/application.yml b/backend/analytics-service/src/main/resources/application.yml index 0a41239..1aa49eb 100644 --- a/backend/analytics-service/src/main/resources/application.yml +++ b/backend/analytics-service/src/main/resources/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8088 spring: @@ -6,7 +11,7 @@ spring: name: analytics-service datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false + url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false username: ${MYSQL_USERNAME:root} password: ${MYSQL_PASSWORD:root} redis: diff --git a/backend/analytics-service/target/analytics-service-1.0.0-SNAPSHOT.jar b/backend/analytics-service/target/analytics-service-1.0.0-SNAPSHOT.jar index 5d259bc..3f3d8bc 100644 Binary files a/backend/analytics-service/target/analytics-service-1.0.0-SNAPSHOT.jar and b/backend/analytics-service/target/analytics-service-1.0.0-SNAPSHOT.jar differ diff --git a/backend/analytics-service/target/analytics-service-1.0.0-SNAPSHOT.jar.original b/backend/analytics-service/target/analytics-service-1.0.0-SNAPSHOT.jar.original new file mode 100644 index 0000000..6652a5f Binary files /dev/null and b/backend/analytics-service/target/analytics-service-1.0.0-SNAPSHOT.jar.original differ diff --git a/backend/analytics-service/target/classes/application.yml b/backend/analytics-service/target/classes/application.yml index dd133e3..1aa49eb 100644 --- a/backend/analytics-service/target/classes/application.yml +++ b/backend/analytics-service/target/classes/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8088 spring: diff --git a/backend/analytics-service/target/classes/com/artedu/analytics/AnalyticsServiceApplication.class b/backend/analytics-service/target/classes/com/artedu/analytics/AnalyticsServiceApplication.class index 40d9bdd..e96882f 100644 Binary files a/backend/analytics-service/target/classes/com/artedu/analytics/AnalyticsServiceApplication.class and b/backend/analytics-service/target/classes/com/artedu/analytics/AnalyticsServiceApplication.class differ diff --git a/backend/analytics-service/target/classes/com/artedu/analytics/controller/AnalyticsController.class b/backend/analytics-service/target/classes/com/artedu/analytics/controller/AnalyticsController.class index 52264c3..7cc1332 100644 Binary files a/backend/analytics-service/target/classes/com/artedu/analytics/controller/AnalyticsController.class and b/backend/analytics-service/target/classes/com/artedu/analytics/controller/AnalyticsController.class differ diff --git a/backend/analytics-service/target/classes/com/artedu/analytics/service/AnalyticsService.class b/backend/analytics-service/target/classes/com/artedu/analytics/service/AnalyticsService.class new file mode 100644 index 0000000..db5b458 Binary files /dev/null and b/backend/analytics-service/target/classes/com/artedu/analytics/service/AnalyticsService.class differ diff --git a/backend/analytics-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/backend/analytics-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index db2e57f..254e889 100644 --- a/backend/analytics-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/backend/analytics-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,2 +1,3 @@ +com\artedu\analytics\service\AnalyticsService.class com\artedu\analytics\AnalyticsServiceApplication.class com\artedu\analytics\controller\AnalyticsController.class diff --git a/backend/analytics-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/backend/analytics-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index cc78904..2008ba4 100644 --- a/backend/analytics-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/backend/analytics-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -1,2 +1,3 @@ D:\www\agent_9art\backend\analytics-service\src\main\java\com\artedu\analytics\AnalyticsServiceApplication.java D:\www\agent_9art\backend\analytics-service\src\main\java\com\artedu\analytics\controller\AnalyticsController.java +D:\www\agent_9art\backend\analytics-service\src\main\java\com\artedu\analytics\service\AnalyticsService.java diff --git a/backend/archive-service/Dockerfile b/backend/archive-service/Dockerfile index 858c6fb..c68d35a 100644 --- a/backend/archive-service/Dockerfile +++ b/backend/archive-service/Dockerfile @@ -1,5 +1,7 @@ -FROM openjdk:17-jdk-slim +FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY target/*.jar app.jar -EXPOSE 8080 +EXPOSE 8082 +# 注意:Linux部署时需将libWeWorkFinanceSdk_Java.so放入容器 +# 可通过 -v /host/sdk:/app/sdk 挂载,并设置 -Djava.library.path=/app/sdk ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/backend/archive-service/SDK_SETUP.md b/backend/archive-service/SDK_SETUP.md new file mode 100644 index 0000000..64cfdb1 --- /dev/null +++ b/backend/archive-service/SDK_SETUP.md @@ -0,0 +1,171 @@ +# 企微会话存档 SDK 对接指南 + +## 一、SDK 下载 + +### 官方下载地址 +- 企微开发者中心:https://developer.work.weixin.qq.com/document/path/91774 +- 下载页面:「获取会话内容」→ SDK 下载 + +### 下载版本选择 +| 环境 | 推荐版本 | 说明 | +|------|---------|------| +| Windows x64 | SDK v3.0 (OpenSSL 3.0) | 2025-02-13 更新 | +| Linux x64 | SDK v3.0 (OpenSSL 3.0) | 2025-02-13 更新 | + +### Windows SDK 文件清单 +解压后 `financeWinSdk/java_sdk/WeWorkFinanceSdk/` 目录下包含: + +| 文件 | 说明 | +|------|------| +| `WeWorkFinanceSdk.dll` | 主 SDK 动态库 | +| `libcrypto-3-x64.dll` | OpenSSL 3.0 依赖 | +| `libssl-3-x64.dll` | OpenSSL 3.0 依赖 | +| `libcurl-x64.dll` | libcurl HTTP 依赖 | +| `Finance.java` | 官方 JNI 封装(本项目使用 JNA,无需此文件) | + +## 二、SDK 放置 + +### Windows 环境 + +将上述 4 个 DLL 文件放到项目目录: + +``` +backend/archive-service/ +└── sdk/ <-- 新建此目录 + ├── WeWorkFinanceSdk.dll + ├── libcrypto-3-x64.dll + ├── libssl-3-x64.dll + └── libcurl-x64.dll +``` + +### Linux 环境 + +```bash +# 将 libWeWorkFinanceSdk_Java.so 重命名并放到系统库路径 +sudo cp libWeWorkFinanceSdk_Java.so /usr/lib/libWeWorkFinanceSdk.so +sudo ldconfig + +# 或者放到项目目录并通过环境变量指定 +mkdir -p backend/archive-service/sdk +cp libWeWorkFinanceSdk_Java.so backend/archive-service/sdk/libWeWorkFinanceSdk.so +``` + +## 三、配置参数 + +### 环境变量方式(推荐) + +```bash +# 企微会话存档 Secret +export WECOM_ARCHIVE_SECRET="your-archive-secret" + +# RSA 私钥(PKCS#1 格式,Base64 编码) +export WECOM_ARCHIVE_RSA_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----" + +# 回调验证 Token(企微后台配置的 Token) +export WECOM_ARCHIVE_CALLBACK_TOKEN="your-callback-token" + +# SDK 路径(指向存放 DLL/SO 的目录) +export WECOM_ARCHIVE_SDK_PATH="D:\www\agent_9art\backend\archive-service\sdk" + +# Mock 模式(开发测试用,无真实 SDK 时设为 true) +export WECOM_ARCHIVE_MOCK=false +``` + +### application.yml 配置项 + +```yaml +wecom: + archive: + corp-id: wwd483c2fba24ae30a # 企业微信 CorpID + secret: ${WECOM_ARCHIVE_SECRET:} # 会话存档 Secret + rsa-private-key: ${WECOM_ARCHIVE_RSA_KEY:} # RSA 私钥 + callback-token: ${WECOM_ARCHIVE_CALLBACK_TOKEN:} # 回调 Token + sdk-path: ${WECOM_ARCHIVE_SDK_PATH:} # SDK 目录路径 + mock-mode: ${WECOM_ARCHIVE_MOCK:false} # Mock 模式开关 +``` + +## 四、参数获取方式 + +### 1. CorpID +企微管理后台 → 「我的企业」→ 「企业ID」 + +### 2. 会话存档 Secret +企微管理后台 → 「管理工具」→ 「会话内容存档」→ 查看 Secret + +### 3. RSA 密钥对 +```bash +# 生成 RSA 私钥(PKCS#1 格式) +openssl genrsa -out private_key.pem 2048 + +# 提取公钥 +openssl rsa -in private_key.pem -pubout -out public_key.pem + +# 将公钥配置到企微后台 +# 将私钥内容配置到 WECOM_ARCHIVE_RSA_KEY 环境变量 +``` + +### 4. 回调 Token +企微后台配置接收消息时设置,用于验证回调请求签名。 + +## 五、Mock 模式(开发/测试) + +当没有真实企微 SDK 或不想连接企微服务器时,启用 Mock 模式: + +```bash +export WECOM_ARCHIVE_MOCK=true +``` + +Mock 模式下: +- 跳过 SDK 初始化,不会报错 +- `pullMessages()` 返回 10 条模拟会话消息 +- 完整的 `saveAndNotify()` 链路正常工作(入库 + MQ + Redis) +- 数据看板可以展示存档统计 + +**测试接口:** +```bash +# 手动触发拉取(Mock 模式下返回模拟数据) +curl -X POST "http://localhost:8082/api/v1/archive/pull" \ + -d "seq=0&limit=10" +``` + +## 六、验证对接成功 + +### 1. 启动服务时日志 +``` +# 真实 SDK 模式 +企微存档SDK初始化成功 + +# Mock 模式 +企微存档Mock模式已启用,跳过SDK初始化 +``` + +### 2. 手动拉取测试 +```bash +curl -X POST "http://localhost:8082/api/v1/archive/pull" \ + -d "seq=0&limit=5" +``` + +### 3. 回调测试(需配置可信域名) +企微后台配置回调 URL 为: +``` +https://your-domain/api/v1/archive/callback +``` + +## 七、常见问题 + +### Q1: 启动报错 `UnsatisfiedLinkError: Unable to load library 'WeWorkFinanceSdk'` +- 检查 `WECOM_ARCHIVE_SDK_PATH` 是否指向正确的 DLL 目录 +- 确认 DLL 文件名正确(Windows 为 `WeWorkFinanceSdk.dll`) +- 确认依赖库(libcrypto/libssl/libcurl)也在同一目录 + +### Q2: SDK 初始化返回非 0 +- 检查 `corp-id` 和 `secret` 是否正确 +- 确认企业已开通「会话内容存档」功能 + +### Q3: 回调签名验证失败 +- 检查 `callback-token` 是否与企微后台配置一致 +- 确认回调 URL 没有被中间件修改参数 + +### Q4: 数据库查询不到存档消息 +- 检查 `archive_messages` 表是否有 `deleted` 字段(逻辑删除) +- MyBatis-Plus 全局配置 `logic-delete-field: deleted` diff --git a/backend/archive-service/pom.xml b/backend/archive-service/pom.xml index 7318d6a..80f4d21 100644 --- a/backend/archive-service/pom.xml +++ b/backend/archive-service/pom.xml @@ -15,6 +15,7 @@ com.arteducommon${project.version} org.springframework.bootspring-boot-starter-web net.java.dev.jnajna + org.springframework.bootspring-boot-starter-testtest diff --git a/backend/archive-service/sdk/.classpath b/backend/archive-service/sdk/.classpath new file mode 100644 index 0000000..51a8bba --- /dev/null +++ b/backend/archive-service/sdk/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/backend/archive-service/sdk/.project b/backend/archive-service/sdk/.project new file mode 100644 index 0000000..d0c2ed2 --- /dev/null +++ b/backend/archive-service/sdk/.project @@ -0,0 +1,17 @@ + + + WeWorkFinanceSdk + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/backend/archive-service/sdk/.settings/org.eclipse.jdt.core.prefs b/backend/archive-service/sdk/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..3a21537 --- /dev/null +++ b/backend/archive-service/sdk/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/backend/archive-service/sdk/META-INF/MANIFEST.MF b/backend/archive-service/sdk/META-INF/MANIFEST.MF new file mode 100644 index 0000000..680a1e2 --- /dev/null +++ b/backend/archive-service/sdk/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: com.company.Main + diff --git a/backend/archive-service/sdk/WeWorkFinanceSdk.dll b/backend/archive-service/sdk/WeWorkFinanceSdk.dll new file mode 100644 index 0000000..4ce7286 Binary files /dev/null and b/backend/archive-service/sdk/WeWorkFinanceSdk.dll differ diff --git a/backend/archive-service/sdk/libcrypto-3-x64.dll b/backend/archive-service/sdk/libcrypto-3-x64.dll new file mode 100644 index 0000000..b1a6dbe Binary files /dev/null and b/backend/archive-service/sdk/libcrypto-3-x64.dll differ diff --git a/backend/archive-service/sdk/libcurl-x64.dll b/backend/archive-service/sdk/libcurl-x64.dll new file mode 100644 index 0000000..8b79e41 Binary files /dev/null and b/backend/archive-service/sdk/libcurl-x64.dll differ diff --git a/backend/archive-service/sdk/src/com/company/Main.java b/backend/archive-service/sdk/src/com/company/Main.java new file mode 100644 index 0000000..8341a8f --- /dev/null +++ b/backend/archive-service/sdk/src/com/company/Main.java @@ -0,0 +1,138 @@ +package com.company; + +import com.tencent.wework.Finance; +import java.io.File; +import java.io.FileOutputStream; + +//10000 ²ÎÊý´íÎó£¬ÇëÇó²ÎÊý´íÎó +//10001 ÍøÂç´íÎó£¬ÍøÂçÇëÇó´íÎó +//10002 Êý¾ݽâÎöʧ°Ü +//10003 ϵͳʧ°Ü +//10004 ÃÜԿ´íÎóµ¼Ö¼ÓÃÜʧ°Ü +//10005 fileid´íÎó +//10006 ½âÃÜʧ°Ü +//10007 ÕҲ»µ½ÏûϢ¼ÓÃܰ汾µÄ˽Կ£¬ÐèҪÖØÐ´«Èë˽Կ¶Ô +//10008 ½âÎöencrypt_key³ö´í +//10009 ip·Ƿ¨ +//10010 Êý¾ݹýÆÚ + +public class Main { + public static void main(String[] args){ + + //seq ±íʾ¸ÃÆóҵ´浵ÏûϢÐòºţ¬¸ÃÐòºŵ¥µ÷µÝÔö£¬À­ȡÐòºŽ¨ÒéÉèÖÃΪÉϴÎÀ­ȡ·µ»ؽá¹ûÖÐ×î´óÐòºš£Ê״ÎÀ­ȡʱseq´«0£¬sdk»᷵»ØÓÐЧÆÚÄÚ×îÔçµÄÏûϢ¡£ + //limit ±íʾ±¾´ÎÀ­ȡµÄ×î´óÏûϢÌõÊý£¬ȡֵ·¶ΧΪ1~1000 + //proxyÓëpasswdΪ´úÀí²ÎÊý£¬Èç¹ûÔËÐÐsdkµĻ·¾³²»ÄÜֱ½ӷÃÎÊÍâÍø£¬ÐèҪÅäÖôúÀí²ÎÊý¡£sdk·ÃÎʵÄÓòÃûÊÇ"https://qyapi.weixin.qq.com"¡£ + //½¨ÒéÏÈͨ¹ýcurl·ÃÎÊ"https://qyapi.weixin.qq.com"£¬Ñé֤´úÀíÅäÖÃÕýȷºó£¬Ôٴ«Èësdk¡£ + //timeout ΪÀ­ȡ»Ự´浵µij¬ʱʱ¼䣬µ¥λΪÃ룬½¨Ò鳬ʱʱ¼äÉèÖÃΪ5s¡£ + //sdkfileid ýÌåÎļþid£¬´ӽâÃܺóµĻỰ´浵Öеõ½ + //savefile ýÌåÎļþ±£´æ·¾¶ + //encrypt_key À­ȡ»Ự´浵·µ»صÄencrypt_random_key£¬ʹÓÃÅäÖÃÔÚÆóҵ΢ÐŹÜÀį́µÄrsa¹«Կ¶ÔӦµÄ˽Կ½âÃܺóµõ½encrypt_key¡£ + //encrypt_chat_msg À­ȡ»Ự´浵·µ»صÄencrypt_chat_msg + if (args.length < 2) { + System.out.println("./sdktools 1(chatmsg) 2(mediadata) 3(decryptdata)\n"); + System.out.println("./sdktools 1 seq limit proxy passwd timeout\n"); + System.out.println("./sdktools 2 fileid proxy passwd timeout savefile\n"); + System.out.println("./sdktools 3 encrypt_key encrypt_chat_msg\n"); + return; + } + + long ret = 0; + //ʹÓÃsdkǰÐèҪ³õʼ»¯£¬³õʼ»¯³ɹ¦ºóµÄsdk¿ÉÒÔһֱʹÓᣠ+ //ÈçÐ貢·¢µ÷ÓÃsdk£¬½¨Òéÿ¸öÏ̳߳ÖÓÐһ¸ösdkʵÀý¡£ + //³õʼ»¯ʱÇëÌîÈë×ԼºÆóҵµÄcorpidÓësecrectkey¡£ + long sdk = Finance.NewSdk(); + ret = Finance.Init(sdk, "w*********d", "zJ*****************************ew"); + if(ret != 0){ + Finance.DestroySdk(sdk); + System.out.println("init sdk err ret " + ret); + return; + } + + if (args[0].equals("1")) { + //À­ȡ»Ự´浵 + int seq = Integer.parseInt(args[1]); + int limit = Integer.parseInt(args[2]); + String proxy = args[3]; + String passwd = args[4]; + int timeout = Integer.parseInt(args[5]); + + //ÿ´ÎʹÓÃGetChatDataÀ­ȡ´浵ǰÐèҪµ÷ÓÃNewSlice»ñȡһ¸öslice£¬ÔÚʹÓÃÍêsliceÖÐÊý¾ݺ󣬻¹ÐèҪµ÷ÓÃFreeSliceÊͷš£ + long slice = Finance.NewSlice(); + ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice); + if (ret != 0) { + System.out.println("getchatdata ret " + ret); + Finance.FreeSlice(slice); + return; + } + System.out.println("getchatdata :" + Finance.GetContentFromSlice(slice)); + Finance.FreeSlice(slice); + } + else if (args[0].equals("2")) { + //À­ȡýÌåÎļþ + String sdkfileid = args[1]; + String proxy = args[2]; + String passwd = args[3]; + int timeout = Integer.parseInt(args[4]); + String savefile = args[5]; + + //ýÌåÎļþÿ´ÎÀ­ȡµÄ×î´ósizeΪ512k£¬Òò´˳¬¹ý512kµÄÎļþÐèҪ·ÖƬÀ­ȡ¡£Èô¸ÃÎļþδÀ­ȡÍêÕû£¬sdkµÄIsMediaDataFinish½ӿڻ᷵»Ø0£¬ͬʱͨ¹ýGetOutIndexBuf½ӿڷµ»ØÏ´ÎÀ­ȡÐèҪ´«ÈëGetMediaDataµÄindexbuf¡£ + //indexbufһ°ã¸ñʽÈçÓҲàËùʾ£¬¡±Range:bytes=524288-1048575¡°£¬±íʾÕâ´ÎÀ­ȡµÄÊǴÓ524288µ½1048575µķÖƬ¡£µ¥¸öÎļþÊ״ÎÀ­ȡÌîдµÄindexbufΪ¿Õ×ַû´®£¬À­ȡºóÐø·ÖƬʱֱ½ÓÌîÈëÉϴηµ»صÄindexbuf¼´¿ɡ£ + String indexbuf = ""; + while(true){ + //ÿ´ÎʹÓÃGetMediaDataÀ­ȡ´浵ǰÐèҪµ÷ÓÃNewMediaData»ñȡһ¸ömedia_data£¬ÔÚʹÓÃÍêmedia_dataÖÐÊý¾ݺ󣬻¹ÐèҪµ÷ÓÃFreeMediaDataÊͷš£ + long media_data = Finance.NewMediaData(); + ret = Finance.GetMediaData(sdk, indexbuf, sdkfileid, proxy, passwd, timeout, media_data); + if(ret!=0){ + System.out.println("getmediadata ret:" + ret); + Finance.FreeMediaData(media_data); + return; + } + System.out.printf("getmediadata outindex len:%d, data_len:%d, is_finis:%d\n",Finance.GetIndexLen(media_data),Finance.GetDataLen(media_data), Finance.IsMediaDataFinish(media_data)); + try { + //´óÓÚ512kµÄÎļþ»á·ÖƬÀ­ȡ£¬´˴¦ÐèҪʹÓÃ׷¼Óд£¬±ÜÃâºóÃæµķÖƬ¸²¸Ç֮ǰµÄÊý¾ݡ£ + FileOutputStream outputStream = new FileOutputStream(new File(savefile), true); + outputStream.write(Finance.GetData(media_data)); + outputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + if(Finance.IsMediaDataFinish(media_data) == 1) + { + //ÒѾ­À­ȡÍê³É×îºóһ¸ö·ÖƬ + Finance.FreeMediaData(media_data); + break; + } + else + { + //»ñȡÏ´ÎÀ­ȡÐèҪʹÓõÄindexbuf + indexbuf = Finance.GetOutIndexBuf(media_data); + Finance.FreeMediaData(media_data); + } + } + } + else if (args[0].equals("3")) { + //½âÃܻỰ´浵ÄÚÈÝ + //sdk²»»áҪÇóÓû§´«Èërsa˽Կ£¬±£֤Óû§»Ự´浵Êý¾ÝֻÓÐ×ԼºÄܹ»½âÃܡ£ + //´˴¦ÐèҪÓû§ÏÈÓÃrsa˽Կ½âÃÜencrypt_random_keyºó£¬×÷Ϊencrypt_key²ÎÊý´«ÈësdkÀ´½âÃÜencrypt_chat_msg»ñȡ»Ự´浵Ã÷Îġ£ + String encrypt_key = args[1]; + String encrypt_chat_msg = args[2]; + + //ÿ´ÎʹÓÃDecryptData½âÃܻỰ´浵ǰÐèҪµ÷ÓÃNewSlice»ñȡһ¸öslice£¬ÔÚʹÓÃÍêsliceÖÐÊý¾ݺ󣬻¹ÐèҪµ÷ÓÃFreeSliceÊͷš£ + long msg = Finance.NewSlice(); + ret = Finance.DecryptData(sdk, encrypt_key, encrypt_chat_msg, msg); + if (ret != 0) { + System.out.println("getchatdata ret " + ret); + Finance.FreeSlice(msg); + return; + } + System.out.println("decrypt ret:" + ret + " msg:" + Finance.GetContentFromSlice(msg)); + Finance.FreeSlice(msg); + } + else { + System.out.println("wrong args " + args[0]); + } + Finance.DestroySdk(sdk); + } +} + diff --git a/backend/archive-service/sdk/src/com/tencent/wework/Finance.java b/backend/archive-service/sdk/src/com/tencent/wework/Finance.java new file mode 100644 index 0000000..a489fa8 --- /dev/null +++ b/backend/archive-service/sdk/src/com/tencent/wework/Finance.java @@ -0,0 +1,128 @@ +package com.tencent.wework; + +/* sdk·µ»ØÊý¾Ý +typedef struct Slice_t { + char* buf; + int len; +} Slice_t; + +typedef struct MediaData { + char* outindexbuf; + int out_len; + char* data; + int data_len; + int is_finish; +} MediaData_t; +*/ + +public class Finance { + public native static long NewSdk(); + + /** + * ³õʼ»¯º¯Êý + * Returnֵ=0±íʾ¸ÃAPIµ÷Óóɹ¦ + * + * @param [in] sdk NewSdk·µ»صÄsdkָÕë + * @param [in] corpid µ÷ÓÃÆóҵµÄÆóҵid£¬ÀýÈ磺wwd08c8exxxx5ab44d£¬¿ÉÒÔÔÚÆóҵ΢ÐŹÜÀí¶Ë--ÎҵÄÆóҵ--ÆóҵÐÅϢ²鿴 + * @param [in] secret ÁÄÌìÄÚÈݴ浵µÄSecret£¬¿ÉÒÔÔÚÆóҵ΢ÐŹÜÀí¶Ë--¹ÜÀí¹¤¾ß--ÁÄÌìÄÚÈݴ浵²鿴 + * @return ·µ»ØÊǷñ³õʼ»¯³ɹ¦ + * 0 - ³ɹ¦ + * !=0 - ʧ°Ü + */ + public native static int Init(long sdk, String corpid, String secret); + + /** + * À­ȡÁÄÌì¼Ç¼º¯Êý + * Returnֵ=0±íʾ¸ÃAPIµ÷Óóɹ¦ + * + * @param [in] sdk NewSdk·µ»صÄsdkָÕë + * @param [in] seq ´Óָ¶¨µÄseq¿ªʼÀ­ȡÏûϢ£¬עÒâµÄÊǷµ»صÄÏûϢ´Óseq+1¿ªʼ·µ»أ¬seqΪ֮ǰ½ӿڷµ»صÄ×î´óseqֵ¡£Ê״ÎʹÓÃÇëʹÓÃseq:0 + * @param [in] limit һ´ÎÀ­ȡµÄÏûϢÌõÊý£¬×î´óֵ1000Ìõ£¬³¬¹ý1000Ìõ»᷵»شíÎó + * @param [in] proxy ʹÓôúÀíµÄÇëÇó£¬ÐèҪ´«Èë´úÀíµÄÁ´½ӡ£È磺socks5://10.0.0.1:8081 »òÕß http://10.0.0.1:8081 + * @param [in] passwd ´úÀíÕ˺ÅÃÜÂ룬ÐèҪ´«Èë´úÀíµÄÕ˺ÅÃÜÂ롣Èç user_name:passwd_123 + * @param [out] chatDatas ·µ»ر¾´ÎÀ­ȡÏûϢµÄÊý¾ݣ¬slice½ṹÌå.ÄÚÈݰüÀ¨errcode/errmsg£¬ÒԼ°ÿÌõÏûϢÄÚÈݡ£ + * @return ·µ»ØÊǷñµ÷Óóɹ¦ + * 0 - ³ɹ¦ + * !=0 - ʧ°Ü + */ + public native static int GetChatData(long sdk, long seq, long limit, String proxy, String passwd, long timeout, long chatData); + + /** + * À­ȡýÌåÏûϢº¯Êý + * Returnֵ=0±íʾ¸ÃAPIµ÷Óóɹ¦ + * + * @param [in] sdk NewSdk·µ»صÄsdkָÕë + * @param [in] sdkFileid ´ÓGetChatData·µ»صÄÁÄÌìÏûϢÖУ¬ýÌåÏûϢ°üÀ¨µÄsdkfileid + * @param [in] proxy ʹÓôúÀíµÄÇëÇó£¬ÐèҪ´«Èë´úÀíµÄÁ´½ӡ£È磺socks5://10.0.0.1:8081 »òÕß http://10.0.0.1:8081 + * @param [in] passwd ´úÀíÕ˺ÅÃÜÂ룬ÐèҪ´«Èë´úÀíµÄÕ˺ÅÃÜÂ롣Èç user_name:passwd_123 + * @param [in] indexbuf ýÌåÏûϢ·ÖƬÀ­ȡ£¬ÐèҪÌîÈëÿ´ÎÀ­ȡµÄË÷ÒýÐÅϢ¡£Ê״β»ÐèҪÌîд£¬ĬÈÏÀ­ȡ512k£¬ºóÐøÿ´ε÷ÓÃֻÐèҪ½«Éϴε÷Ó÷µ»صÄoutindexbufÌîÈ뼴¿ɡ£ + * @param [out] media_data ·µ»ر¾´ÎÀ­ȡµÄýÌåÊý¾Ý.MediaData½ṹÌå.ÄÚÈݰüÀ¨data(Êý¾ÝÄÚÈÝ)/outindexbuf(Ï´ÎË÷Òý)/is_finish(À­ȡÍê³ɱê¼Ç) + * @return ·µ»ØÊǷñµ÷Óóɹ¦ + * 0 - ³ɹ¦ + * !=0 - ʧ°Ü + */ + public native static int GetMediaData(long sdk, String indexbuf, String sdkField, String proxy, String passwd, long timeout, long mediaData); + + /** + * @param [in] encrypt_key, getchatdata·µ»صÄencrypt_key + * @param [in] encrypt_msg, getchatdata·µ»صÄcontent + * @param [out] msg, ½âÃܵÄÏûϢÃ÷ÎÄ + * @return ·µ»ØÊǷñµ÷Óóɹ¦ + * 0 - ³ɹ¦ + * !=0 - ʧ°Ü + * @brief ½âÎöÃÜÎÄ + */ + public native static int DecryptData(long sdk, String encrypt_key, String encrypt_msg, long msg); + + public native static void DestroySdk(long sdk); + + public native static long NewSlice(); + + /** + * @return + * @brief ÊͷÅslice£¬ºÍNewSlice³ɶÔʹÓà + */ + public native static void FreeSlice(long slice); + + /** + * @return ÄÚÈÝ + * @brief »ñȡsliceÄÚÈÝ + */ + public native static String GetContentFromSlice(long slice); + + /** + * @return ÄÚÈÝ + * @brief »ñȡsliceÄÚÈݳ¤¶È + */ + public native static int GetSliceLen(long slice); + + public native static long NewMediaData(); + + public native static void FreeMediaData(long mediaData); + + /** + * @return outindex + * @brief »ñȡmediadata outindex + */ + public native static String GetOutIndexBuf(long mediaData); + + /** + * @return data + * @brief »ñȡmediadata dataÊý¾Ý + */ + public native static byte[] GetData(long mediaData); + + public native static int GetIndexLen(long mediaData); + + public native static int GetDataLen(long mediaData); + + /** + * @return 1Íê³ɡ¢0δÍê³É + * @brief ÅжÏmediadataÊǷñ½áÊø + */ + public native static int IsMediaDataFinish(long mediaData); + + static { + System.loadLibrary("WeWorkFinanceSdk"); + } +} \ No newline at end of file diff --git a/backend/archive-service/src/main/java/com/artedu/archive/controller/ArchiveCallbackController.java b/backend/archive-service/src/main/java/com/artedu/archive/controller/ArchiveCallbackController.java index acd7b55..611599a 100644 --- a/backend/archive-service/src/main/java/com/artedu/archive/controller/ArchiveCallbackController.java +++ b/backend/archive-service/src/main/java/com/artedu/archive/controller/ArchiveCallbackController.java @@ -4,8 +4,14 @@ import com.artedu.archive.service.ArchivePullService; import com.artedu.common.result.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import javax.validation.constraints.NotBlank; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.Arrays; import java.util.concurrent.CompletableFuture; /** @@ -13,6 +19,7 @@ import java.util.concurrent.CompletableFuture; * 接收企微的msgaudit_notify回调 */ @Slf4j +@Validated @RestController @RequestMapping("/api/v1/archive") public class ArchiveCallbackController { @@ -20,15 +27,47 @@ public class ArchiveCallbackController { @Autowired private ArchivePullService archivePullService; + @Value("${wecom.archive.callback-token:}") + private String callbackToken; + + /** + * 企微回调URL验证(GET请求) + */ + @GetMapping("/callback") + public String verifyUrl( + @NotBlank @RequestParam("msg_signature") String msgSignature, + @NotBlank @RequestParam("timestamp") String timestamp, + @NotBlank @RequestParam("nonce") String nonce, + @NotBlank @RequestParam("echostr") String echostr) { + + log.info("收到企微回调URL验证: msg_signature={}, timestamp={}, nonce={}", msgSignature, timestamp, nonce); + + if (!verifySignature(msgSignature, timestamp, nonce, echostr)) { + log.error("回调URL验证签名失败"); + return "fail"; + } + + log.info("回调URL验证成功"); + return echostr; + } + + /** + * 企微回调消息接收(POST请求) + */ @PostMapping("/callback") public Result callback( - @RequestParam("msg_signature") String msgSignature, - @RequestParam("timestamp") String timestamp, - @RequestParam("nonce") String nonce, + @NotBlank @RequestParam("msg_signature") String msgSignature, + @NotBlank @RequestParam("timestamp") String timestamp, + @NotBlank @RequestParam("nonce") String nonce, @RequestBody String requestBody) { log.debug("收到企微回调: msg_signature={}, timestamp={}, nonce={}", msgSignature, timestamp, nonce); + if (!verifySignature(msgSignature, timestamp, nonce, requestBody)) { + log.error("回调消息签名验证失败"); + return Result.fail("签名验证失败"); + } + if (requestBody.contains("msgaudit_notify")) { log.info("收到msgaudit_notify回调,触发存档拉取"); @@ -47,6 +86,35 @@ public class ArchiveCallbackController { return Result.success("success"); } + /** + * 验证企微回调签名 + * 算法:SHA1(sort(token, timestamp, nonce, msg)) + */ + private boolean verifySignature(String signature, String timestamp, String nonce, String msg) { + if (callbackToken == null || callbackToken.isEmpty()) { + log.warn("回调验证token未配置,跳过签名验证"); + return true; + } + try { + String[] arr = new String[]{callbackToken, timestamp, nonce, msg}; + Arrays.sort(arr); + StringBuilder content = new StringBuilder(); + for (String s : arr) { + content.append(s); + } + MessageDigest md = MessageDigest.getInstance("SHA1"); + byte[] digest = md.digest(content.toString().getBytes(StandardCharsets.UTF_8)); + StringBuilder hex = new StringBuilder(); + for (byte b : digest) { + hex.append(String.format("%02x", b)); + } + return hex.toString().equals(signature); + } catch (Exception e) { + log.error("签名验证异常: {}", e.getMessage()); + return false; + } + } + @PostMapping("/pull") public Result manualPull( @RequestParam(value = "seq", required = false) Long seq, diff --git a/backend/archive-service/src/main/java/com/artedu/archive/entity/ArchiveMessage.java b/backend/archive-service/src/main/java/com/artedu/archive/entity/ArchiveMessage.java index 5e3e5a8..7076ea2 100644 --- a/backend/archive-service/src/main/java/com/artedu/archive/entity/ArchiveMessage.java +++ b/backend/archive-service/src/main/java/com/artedu/archive/entity/ArchiveMessage.java @@ -2,6 +2,7 @@ package com.artedu.archive.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -34,6 +35,8 @@ public class ArchiveMessage { private String sessionId; private Integer decryptStatus; private String decryptError; + @TableLogic + private Integer deleted; private LocalDateTime createdAt; private LocalDateTime updatedAt; } diff --git a/backend/archive-service/src/main/java/com/artedu/archive/sdk/WeWorkFinanceSdk.java b/backend/archive-service/src/main/java/com/artedu/archive/sdk/WeWorkFinanceSdk.java deleted file mode 100644 index aeb5847..0000000 --- a/backend/archive-service/src/main/java/com/artedu/archive/sdk/WeWorkFinanceSdk.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.artedu.archive.sdk; - -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; - -import java.util.Arrays; -import java.util.List; - -/** - * 企微会话存档C SDK的JNA接口定义 - * 通过JNA调用本地动态库(.so/.dll) - */ -public interface WeWorkFinanceSdk extends Library { - - WeWorkFinanceSdk INSTANCE = Native.load( - System.getProperty("os.name").toLowerCase().contains("win") ? "WeWorkFinanceSdk" : "libWeWorkFinanceSdk", - WeWorkFinanceSdk.class - ); - - long NewSdk(); - - int Init(long sdk, String corpId, String secret); - - int GetChatData(long sdk, long seq, int limit, String proxy, String passwd, int timeout, long chatData); - - int DecryptData(long sdk, String encryptKey, String encryptMsg, long msg); - - int DestroySdk(long sdk); - - void FreeSlice(long slice); - - class Slice_t extends Structure { - public Pointer content; - public int len; - - @Override - protected List getFieldOrder() { - return Arrays.asList("content", "len"); - } - } -} diff --git a/backend/archive-service/src/main/java/com/artedu/archive/service/ArchivePullService.java b/backend/archive-service/src/main/java/com/artedu/archive/service/ArchivePullService.java index df14875..26b8dca 100644 --- a/backend/archive-service/src/main/java/com/artedu/archive/service/ArchivePullService.java +++ b/backend/archive-service/src/main/java/com/artedu/archive/service/ArchivePullService.java @@ -2,10 +2,10 @@ package com.artedu.archive.service; import com.artedu.archive.entity.ArchiveMessage; import com.artedu.archive.mapper.ArchiveMessageMapper; -import com.artedu.archive.sdk.WeWorkFinanceSdk; import com.artedu.archive.util.RsaDecryptUtil; import com.artedu.common.config.RabbitConfig; import com.artedu.common.util.JsonUtils; +import com.tencent.wework.Finance; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; @@ -15,6 +15,7 @@ import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; +import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -22,7 +23,7 @@ import java.util.concurrent.TimeUnit; /** * 存档拉取服务 - * 通过C SDK拉取企微会话存档消息 + * 通过企微官方JNI SDK (com.tencent.wework.Finance) 拉取会话存档消息 */ @Slf4j @Service @@ -37,6 +38,12 @@ public class ArchivePullService { @Value("${wecom.archive.rsa-private-key:}") private String rsaPrivateKey; + @Value("${wecom.archive.sdk-path:}") + private String sdkPath; + + @Value("${wecom.archive.mock-mode:false}") + private boolean mockMode; + @Autowired private ArchiveMessageMapper archiveMessageMapper; @@ -54,33 +61,102 @@ public class ArchivePullService { @PostConstruct public void init() { - sdk = WeWorkFinanceSdk.INSTANCE.NewSdk(); - int ret = WeWorkFinanceSdk.INSTANCE.Init(sdk, corpId, secret); - if (ret != 0) { - log.error("企微存档SDK初始化失败, ret={}", ret); - throw new RuntimeException("SDK初始化失败: " + ret); + if (mockMode) { + log.info("企微存档Mock模式已启用,跳过SDK初始化"); + sdk = 0; + return; + } + if (secret == null || secret.isEmpty()) { + log.warn("企微存档Secret未配置,跳过SDK初始化"); + return; + } + try { + // 设置 java.library.path,确保 JNI 能找到 DLL + setupLibraryPath(); + + sdk = Finance.NewSdk(); + int ret = Finance.Init(sdk, corpId, secret); + if (ret != 0) { + log.error("企微存档SDK初始化失败, ret={}", ret); + sdk = 0; + } else { + log.info("企微存档SDK初始化成功"); + } + } catch (UnsatisfiedLinkError e) { + log.error("企微存档SDK原生库加载失败: {}", e.getMessage()); + sdk = 0; + } + } + + /** + * 将 sdk-path 添加到 java.library.path,使 JNI System.loadLibrary 能找到 DLL + */ + private void setupLibraryPath() { + if (sdkPath == null || sdkPath.isEmpty()) { + return; + } + File sdkDir = new File(sdkPath); + if (!sdkDir.exists() || !sdkDir.isDirectory()) { + log.warn("SDK路径不存在: {}", sdkPath); + return; + } + String existingPath = System.getProperty("java.library.path", ""); + if (!existingPath.contains(sdkPath)) { + String newPath = existingPath.isEmpty() ? sdkPath : existingPath + File.pathSeparator + sdkPath; + System.setProperty("java.library.path", newPath); + // 刷新 JNA / JNI 的库路径缓存 + try { + java.lang.reflect.Field field = ClassLoader.class.getDeclaredField("usr_paths"); + field.setAccessible(true); + field.set(null, null); + } catch (Exception e) { + log.warn("刷新java.library.path缓存失败: {}", e.getMessage()); + } + log.info("已将SDK路径添加到java.library.path: {}", sdkPath); } - log.info("企微存档SDK初始化成功"); } @PreDestroy public void destroy() { - WeWorkFinanceSdk.INSTANCE.DestroySdk(sdk); - log.info("企微存档SDK已释放"); + if (sdk != 0) { + Finance.DestroySdk(sdk); + log.info("企微存档SDK已释放"); + } + } + + public boolean isMockMode() { + return mockMode || sdk == 0; } public List pullMessages(long seq, int limit) { List messages = new ArrayList<>(); - long chatDataSlice = 0; + + // Mock 模式:返回模拟数据 + if (mockMode || sdk == 0) { + if (mockMode) { + log.info("Mock模式:生成模拟存档消息, seq={}, limit={}", seq, limit); + return MockArchiveDataProvider.generate(seq, limit, corpId); + } + log.warn("企微存档SDK未初始化,跳过拉取"); + return messages; + } + + long chatDatas = 0; try { - int ret = WeWorkFinanceSdk.INSTANCE.GetChatData(sdk, seq, limit, null, null, 5, chatDataSlice); + chatDatas = Finance.NewSlice(); + if (chatDatas == 0) { + log.error("NewSlice返回0,内存不足"); + return messages; + } + + int ret = Finance.GetChatData(sdk, seq, limit, null, null, 5, chatDatas); if (ret != 0) { log.error("GetChatData失败, ret={}, seq={}", ret, seq); return messages; } - String jsonData = extractFromSlice(chatDataSlice); + String jsonData = Finance.GetContentFromSlice(chatDatas); if (jsonData == null || jsonData.isEmpty()) { return messages; } @@ -110,8 +186,8 @@ public class ArchivePullService { log.info("拉取存档消息完成: seq={}, limit={}, 成功解析{}条", seq, limit, messages.size()); } finally { - if (chatDataSlice != 0) { - WeWorkFinanceSdk.INSTANCE.FreeSlice(chatDataSlice); + if (chatDatas != 0) { + Finance.FreeSlice(chatDatas); } } @@ -171,24 +247,25 @@ public class ArchivePullService { private String decryptMessage(String encryptKey, String encryptMsg) { long msgSlice = 0; try { - int ret = WeWorkFinanceSdk.INSTANCE.DecryptData(sdk, encryptKey, encryptMsg, msgSlice); + msgSlice = Finance.NewSlice(); + if (msgSlice == 0) { + log.error("NewSlice返回0,内存不足"); + return null; + } + // 官方JNI: DecryptData(long sdk, String encrypt_key, String encrypt_msg, long msg) + int ret = Finance.DecryptData(sdk, encryptKey, encryptMsg, msgSlice); if (ret != 0) { log.error("DecryptData失败, ret={}", ret); return null; } - return extractFromSlice(msgSlice); + return Finance.GetContentFromSlice(msgSlice); } finally { if (msgSlice != 0) { - WeWorkFinanceSdk.INSTANCE.FreeSlice(msgSlice); + Finance.FreeSlice(msgSlice); } } } - private String extractFromSlice(long slice) { - // 实际项目中需要根据JNA内存布局正确实现 - return ""; - } - private String detectRole(String fromUser) { if (fromUser != null && fromUser.startsWith("wm")) { return "EXTERNAL"; diff --git a/backend/archive-service/src/main/java/com/artedu/archive/service/MockArchiveDataProvider.java b/backend/archive-service/src/main/java/com/artedu/archive/service/MockArchiveDataProvider.java new file mode 100644 index 0000000..dcdcde6 --- /dev/null +++ b/backend/archive-service/src/main/java/com/artedu/archive/service/MockArchiveDataProvider.java @@ -0,0 +1,76 @@ +package com.artedu.archive.service; + +import com.artedu.archive.entity.ArchiveMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * Mock 存档数据生成器 + * 当没有真实企微 SDK 时,生成模拟会话存档消息用于测试链路 + */ +public class MockArchiveDataProvider { + + private static final Logger log = LoggerFactory.getLogger(MockArchiveDataProvider.class); + + private static final String[] MOCK_MSG_TYPES = {"text", "text", "text", "image", "voice"}; + private static final String[] MOCK_CONTENTS = { + "你好,我想咨询一下原画课程", + "好的,我们这边有2D原画和3D建模两个方向", + "2D原画班的学费是多少呢?", + "2D原画班学费是16800,现在报名有优惠", + "那上课时间是怎样的?", + "每周一到周五,上午9点到下午6点", + "可以试听吗?", + "可以的,我们提供免费试听一周", + "好的,我先考虑一下", + "没问题,有问题随时联系我" + }; + private static final String[] MOCK_FROM_USERS = {"wx_001", "wm1234567890abcdef"}; + private static final String[] MOCK_TO_USERS = {"staff_001", "wx_001"}; + + /** + * 生成模拟存档消息 + * + * @param startSeq 起始 seq + * @param limit 最大条数 + * @param corpId 企业ID + * @return 模拟消息列表 + */ + public static List generate(long startSeq, int limit, String corpId) { + List messages = new ArrayList<>(); + int count = Math.min(limit, MOCK_CONTENTS.length); + long now = System.currentTimeMillis(); + + for (int i = 0; i < count; i++) { + long seq = startSeq + i + 1; + boolean isFromCustomer = i % 2 == 0; + String fromUser = isFromCustomer ? MOCK_FROM_USERS[0] : MOCK_FROM_USERS[1]; + String toUser = isFromCustomer ? MOCK_TO_USERS[0] : MOCK_TO_USERS[1]; + + ArchiveMessage msg = new ArchiveMessage(); + msg.setMsgid("mock_" + corpId + "_" + seq); + msg.setSeq(seq); + msg.setCorpId(corpId); + msg.setAction("send"); + msg.setFromUser(fromUser); + msg.setFromRole(fromUser.startsWith("wm") ? "EXTERNAL" : "INTERNAL"); + msg.setToUser(toUser); + msg.setTolist("[\"" + toUser + "\"]"); + msg.setMsgtype(MOCK_MSG_TYPES[i % MOCK_MSG_TYPES.length]); + msg.setMsgtime(now - (count - i) * 60000L); + msg.setContent(MOCK_CONTENTS[i]); + msg.setDecryptStatus(1); + msg.setSessionId(corpId + "_" + fromUser + "_" + toUser); + msg.setCreatedAt(LocalDateTime.now()); + msg.setUpdatedAt(LocalDateTime.now()); + messages.add(msg); + } + + log.info("Mock模式生成存档消息: startSeq={}, limit={}, 实际生成{}条", startSeq, limit, messages.size()); + return messages; + } +} diff --git a/backend/archive-service/src/main/java/com/tencent/wework/Finance.java b/backend/archive-service/src/main/java/com/tencent/wework/Finance.java new file mode 100644 index 0000000..08fb713 --- /dev/null +++ b/backend/archive-service/src/main/java/com/tencent/wework/Finance.java @@ -0,0 +1,117 @@ +package com.tencent.wework; + +/* +typedef struct Slice_t { + char* buf; + int len; +} Slice_t; + +typedef struct MediaData { + char* outindexbuf; + int out_len; + char* data; + int data_len; + int is_finish; +} MediaData_t; +*/ + +public class Finance { + public native static long NewSdk(); + + /** + * Initialize SDK + * Return 0 = success + * + * @param sdk SDK handle returned by NewSdk + * @param corpid Enterprise WeChat CorpID + * @param secret Session archive secret + * @return 0 = success, != 0 = fail + */ + public native static int Init(long sdk, String corpid, String secret); + + /** + * Fetch encrypted session archive data + * Return 0 = success + * + * @param sdk SDK handle + * @param seq Start sequence number (use 0 for first request) + * @param limit Max messages per request (max 1000) + * @param proxy Proxy URL (optional) + * @param passwd Proxy credentials (optional) + * @param timeout Timeout in seconds + * @param chatDatas Output slice containing chat data JSON + * @return 0 = success, != 0 = fail + */ + public native static int GetChatData(long sdk, long seq, long limit, String proxy, String passwd, long timeout, long chatData); + + /** + * Fetch media data + * + * @param sdk SDK handle + * @param indexbuf Media index buffer + * @param sdkFileid Media file ID from chat data + * @param proxy Proxy URL (optional) + * @param passwd Proxy credentials (optional) + * @param timeout Timeout in seconds + * @param mediaData Output MediaData structure + * @return 0 = success, != 0 = fail + */ + public native static int GetMediaData(long sdk, String indexbuf, String sdkField, String proxy, String passwd, long timeout, long mediaData); + + /** + * Decrypt session archive message + * + * @param encrypt_key encrypt_random_key from GetChatData, decrypted with RSA private key + * @param encrypt_msg encrypt_chat_msg from GetChatData + * @param msg Output slice containing decrypted message JSON + * @return 0 = success, != 0 = fail + */ + public native static int DecryptData(long sdk, String encrypt_key, String encrypt_msg, long msg); + + public native static void DestroySdk(long sdk); + + public native static long NewSlice(); + + /** + * Free slice (use with NewSlice) + */ + public native static void FreeSlice(long slice); + + /** + * Get content from slice as String + */ + public native static String GetContentFromSlice(long slice); + + /** + * Get content length from slice + */ + public native static int GetSliceLen(long slice); + + public native static long NewMediaData(); + + public native static void FreeMediaData(long mediaData); + + /** + * Get outindexbuf from mediaData + */ + public native static String GetOutIndexBuf(long mediaData); + + /** + * Get data bytes from mediaData + */ + public native static byte[] GetData(long mediaData); + + public native static int GetIndexLen(long mediaData); + + public native static int GetDataLen(long mediaData); + + /** + * Check if mediaData download is finished + * @return 1 = finished, 0 = not finished + */ + public native static int IsMediaDataFinish(long mediaData); + + static { + System.loadLibrary("WeWorkFinanceSdk"); + } +} diff --git a/backend/archive-service/src/main/java/com/tencent/wework/Finance.java.utf8 b/backend/archive-service/src/main/java/com/tencent/wework/Finance.java.utf8 new file mode 100644 index 0000000..e69de29 diff --git a/backend/archive-service/src/main/resources/application.yml b/backend/archive-service/src/main/resources/application.yml index 420d1bc..3d1afff 100644 --- a/backend/archive-service/src/main/resources/application.yml +++ b/backend/archive-service/src/main/resources/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8082 spring: @@ -6,7 +11,7 @@ spring: name: archive-service datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false + url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false username: ${MYSQL_USERNAME:root} password: ${MYSQL_PASSWORD:root} redis: @@ -25,6 +30,15 @@ spring: username: ${RABBITMQ_USERNAME:admin} password: ${RABBITMQ_PASSWORD:admin} +wecom: + archive: + corp-id: wwd483c2fba24ae30a + secret: ${WECOM_ARCHIVE_SECRET:} + rsa-private-key: ${WECOM_ARCHIVE_RSA_KEY:} + callback-token: ${WECOM_ARCHIVE_CALLBACK_TOKEN:} + sdk-path: ${WECOM_ARCHIVE_SDK_PATH:} + mock-mode: ${WECOM_ARCHIVE_MOCK:false} + mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl diff --git a/backend/archive-service/src/test/java/com/artedu/archive/controller/ArchiveCallbackControllerTest.java b/backend/archive-service/src/test/java/com/artedu/archive/controller/ArchiveCallbackControllerTest.java new file mode 100644 index 0000000..2ec035a --- /dev/null +++ b/backend/archive-service/src/test/java/com/artedu/archive/controller/ArchiveCallbackControllerTest.java @@ -0,0 +1,122 @@ +package com.artedu.archive.controller; + +import com.artedu.archive.service.ArchivePullService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.Arrays; + +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest(properties = { + "wecom.archive.callback-token=test-token", + "spring.data.redis.repositories.enabled=false", + "spring.rabbitmq.listener.simple.auto-startup=false" +}) +@AutoConfigureMockMvc +public class ArchiveCallbackControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private ArchivePullService archivePullService; + + @Test + void testVerifyUrlSuccess() throws Exception { + String token = "test-token"; + String timestamp = "1234567890"; + String nonce = "nonce123"; + String echostr = "hello-world"; + String signature = sha1(token, timestamp, nonce, echostr); + + mockMvc.perform(get("/api/v1/archive/callback") + .param("msg_signature", signature) + .param("timestamp", timestamp) + .param("nonce", nonce) + .param("echostr", echostr)) + .andExpect(status().isOk()) + .andExpect(content().string(echostr)); + } + + @Test + void testVerifyUrlInvalidSignature() throws Exception { + mockMvc.perform(get("/api/v1/archive/callback") + .param("msg_signature", "invalid") + .param("timestamp", "123") + .param("nonce", "nonce") + .param("echostr", "test")) + .andExpect(status().isOk()) + .andExpect(content().string("fail")); + } + + @Test + void testCallbackWithMsgauditNotify() throws Exception { + when(archivePullService.getLastSeq()).thenReturn(0L); + when(archivePullService.pullMessages(anyLong(), anyInt())).thenReturn(java.util.List.of()); + + String body = "msgaudit_notify"; + String token = "test-token"; + String timestamp = "123"; + String nonce = "nonce"; + String signature = sha1(token, timestamp, nonce, body); + + mockMvc.perform(post("/api/v1/archive/callback") + .param("msg_signature", signature) + .param("timestamp", timestamp) + .param("nonce", nonce) + .content(body)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)); + + // 异步执行,短暂等待验证方法被调用 + Thread.sleep(500); + verify(archivePullService, atLeastOnce()).getLastSeq(); + } + + @Test + void testManualPull() throws Exception { + when(archivePullService.getLastSeq()).thenReturn(5L); + when(archivePullService.pullMessages(5L, 100)).thenReturn(java.util.List.of()); + + mockMvc.perform(post("/api/v1/archive/pull") + .param("seq", "5") + .param("limit", "100")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").value("拉取完成,共0条消息")); + } + + @Test + void testCallbackMissingParam_shouldReturn400() throws Exception { + mockMvc.perform(post("/api/v1/archive/callback") + .param("timestamp", "123") + .param("nonce", "nonce") + .content("test")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(400)); + } + + private String sha1(String... arr) throws Exception { + Arrays.sort(arr); + StringBuilder content = new StringBuilder(); + for (String s : arr) { + content.append(s); + } + MessageDigest md = MessageDigest.getInstance("SHA1"); + byte[] digest = md.digest(content.toString().getBytes(StandardCharsets.UTF_8)); + StringBuilder hex = new StringBuilder(); + for (byte b : digest) { + hex.append(String.format("%02x", b)); + } + return hex.toString(); + } +} diff --git a/backend/archive-service/src/test/java/com/artedu/archive/service/ArchivePullServiceTest.java b/backend/archive-service/src/test/java/com/artedu/archive/service/ArchivePullServiceTest.java new file mode 100644 index 0000000..976ddc7 --- /dev/null +++ b/backend/archive-service/src/test/java/com/artedu/archive/service/ArchivePullServiceTest.java @@ -0,0 +1,118 @@ +package com.artedu.archive.service; + +import com.artedu.archive.entity.ArchiveMessage; +import com.artedu.archive.mapper.ArchiveMessageMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.amqp.rabbit.core.RabbitTemplate; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@SpringBootTest(properties = { + "wecom.archive.mock-mode=true", + "wecom.archive.corp-id=wwd483c2fba24ae30a", + "spring.data.redis.repositories.enabled=false", + "spring.rabbitmq.listener.simple.auto-startup=false" +}) +public class ArchivePullServiceTest { + + @Autowired + private ArchivePullService archivePullService; + + @MockBean + private ArchiveMessageMapper archiveMessageMapper; + + @MockBean + private RedisTemplate redisTemplate; + + @MockBean + private RabbitTemplate rabbitTemplate; + + @Test + void testMockModeEnabled() { + assertTrue(archivePullService.isMockMode(), "Mock模式应已启用"); + } + + @Test + void testPullMessagesMockData() { + List messages = archivePullService.pullMessages(0, 5); + assertNotNull(messages); + assertFalse(messages.isEmpty(), "Mock模式应返回模拟数据"); + assertEquals(5, messages.size()); + + ArchiveMessage first = messages.get(0); + assertNotNull(first.getMsgid()); + assertEquals(1L, first.getSeq()); + assertEquals("wwd483c2fba24ae30a", first.getCorpId()); + assertNotNull(first.getContent()); + assertNotNull(first.getFromUser()); + } + + @Test + void testPullMessagesMockDataPagination() { + List page1 = archivePullService.pullMessages(0, 3); + List page2 = archivePullService.pullMessages(3, 3); + + assertEquals(3, page1.size()); + assertEquals(3, page2.size()); + assertEquals(1L, page1.get(0).getSeq()); + assertEquals(4L, page2.get(0).getSeq()); + } + + @Test + void testGetLastSeqFromRedis() { + when(redisTemplate.opsForValue()).thenReturn(mock(org.springframework.data.redis.core.ValueOperations.class)); + when(redisTemplate.opsForValue().get("archive:last_seq:wwd483c2fba24ae30a")).thenReturn(100L); + + long seq = archivePullService.getLastSeq(); + assertEquals(100L, seq); + } + + @Test + void testGetLastSeqFromDbFallback() { + when(redisTemplate.opsForValue()).thenReturn(mock(org.springframework.data.redis.core.ValueOperations.class)); + when(redisTemplate.opsForValue().get("archive:last_seq:wwd483c2fba24ae30a")).thenReturn(null); + when(archiveMessageMapper.selectMaxSeq("wwd483c2fba24ae30a")).thenReturn(50L); + + long seq = archivePullService.getLastSeq(); + assertEquals(50L, seq); + } + + @Test + void testSaveAndNotifyDedup() { + ArchiveMessage msg = new ArchiveMessage(); + msg.setMsgid("mock_001"); + msg.setCorpId("wwd483c2fba24ae30a"); + msg.setSeq(1L); + + when(archiveMessageMapper.countByMsgId("mock_001", "wwd483c2fba24ae30a")).thenReturn(1); + + archivePullService.saveAndNotify(List.of(msg)); + + verify(archiveMessageMapper, never()).insert(any()); + } + + @Test + void testSaveAndNotifyInsert() { + ArchiveMessage msg = new ArchiveMessage(); + msg.setMsgid("mock_002"); + msg.setCorpId("wwd483c2fba24ae30a"); + msg.setSeq(2L); + + when(archiveMessageMapper.countByMsgId("mock_002", "wwd483c2fba24ae30a")).thenReturn(0); + + archivePullService.saveAndNotify(List.of(msg)); + + verify(archiveMessageMapper, times(1)).insert(msg); + verify(rabbitTemplate, times(1)).convertAndSend(anyString(), anyString(), anyString()); + } +} diff --git a/backend/archive-service/target/archive-service-1.0.0-SNAPSHOT.jar b/backend/archive-service/target/archive-service-1.0.0-SNAPSHOT.jar index 06fe0b2..1a68183 100644 Binary files a/backend/archive-service/target/archive-service-1.0.0-SNAPSHOT.jar and b/backend/archive-service/target/archive-service-1.0.0-SNAPSHOT.jar differ diff --git a/backend/archive-service/target/archive-service-1.0.0-SNAPSHOT.jar.original b/backend/archive-service/target/archive-service-1.0.0-SNAPSHOT.jar.original new file mode 100644 index 0000000..3691a5b Binary files /dev/null and b/backend/archive-service/target/archive-service-1.0.0-SNAPSHOT.jar.original differ diff --git a/backend/archive-service/target/classes/application.yml b/backend/archive-service/target/classes/application.yml index fc3437f..3d1afff 100644 --- a/backend/archive-service/target/classes/application.yml +++ b/backend/archive-service/target/classes/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8082 spring: @@ -25,6 +30,15 @@ spring: username: ${RABBITMQ_USERNAME:admin} password: ${RABBITMQ_PASSWORD:admin} +wecom: + archive: + corp-id: wwd483c2fba24ae30a + secret: ${WECOM_ARCHIVE_SECRET:} + rsa-private-key: ${WECOM_ARCHIVE_RSA_KEY:} + callback-token: ${WECOM_ARCHIVE_CALLBACK_TOKEN:} + sdk-path: ${WECOM_ARCHIVE_SDK_PATH:} + mock-mode: ${WECOM_ARCHIVE_MOCK:false} + mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl diff --git a/backend/archive-service/target/classes/com/artedu/archive/ArchiveServiceApplication.class b/backend/archive-service/target/classes/com/artedu/archive/ArchiveServiceApplication.class index 2299073..0df7c57 100644 Binary files a/backend/archive-service/target/classes/com/artedu/archive/ArchiveServiceApplication.class and b/backend/archive-service/target/classes/com/artedu/archive/ArchiveServiceApplication.class differ diff --git a/backend/archive-service/target/classes/com/artedu/archive/controller/ArchiveCallbackController.class b/backend/archive-service/target/classes/com/artedu/archive/controller/ArchiveCallbackController.class index e192e25..5e0074d 100644 Binary files a/backend/archive-service/target/classes/com/artedu/archive/controller/ArchiveCallbackController.class and b/backend/archive-service/target/classes/com/artedu/archive/controller/ArchiveCallbackController.class differ diff --git a/backend/archive-service/target/classes/com/artedu/archive/entity/ArchiveMessage.class b/backend/archive-service/target/classes/com/artedu/archive/entity/ArchiveMessage.class index f198faf..1f397a1 100644 Binary files a/backend/archive-service/target/classes/com/artedu/archive/entity/ArchiveMessage.class and b/backend/archive-service/target/classes/com/artedu/archive/entity/ArchiveMessage.class differ diff --git a/backend/archive-service/target/classes/com/artedu/archive/mapper/ArchiveMessageMapper.class b/backend/archive-service/target/classes/com/artedu/archive/mapper/ArchiveMessageMapper.class index 485b423..f0f3f3b 100644 Binary files a/backend/archive-service/target/classes/com/artedu/archive/mapper/ArchiveMessageMapper.class and b/backend/archive-service/target/classes/com/artedu/archive/mapper/ArchiveMessageMapper.class differ diff --git a/backend/archive-service/target/classes/com/artedu/archive/sdk/WeWorkFinanceSdk$Slice_t.class b/backend/archive-service/target/classes/com/artedu/archive/sdk/WeWorkFinanceSdk$Slice_t.class deleted file mode 100644 index 9b165d6..0000000 Binary files a/backend/archive-service/target/classes/com/artedu/archive/sdk/WeWorkFinanceSdk$Slice_t.class and /dev/null differ diff --git a/backend/archive-service/target/classes/com/artedu/archive/sdk/WeWorkFinanceSdk.class b/backend/archive-service/target/classes/com/artedu/archive/sdk/WeWorkFinanceSdk.class deleted file mode 100644 index 0e95592..0000000 Binary files a/backend/archive-service/target/classes/com/artedu/archive/sdk/WeWorkFinanceSdk.class and /dev/null differ diff --git a/backend/archive-service/target/classes/com/artedu/archive/service/ArchivePullService.class b/backend/archive-service/target/classes/com/artedu/archive/service/ArchivePullService.class index 7cda428..50ba87e 100644 Binary files a/backend/archive-service/target/classes/com/artedu/archive/service/ArchivePullService.class and b/backend/archive-service/target/classes/com/artedu/archive/service/ArchivePullService.class differ diff --git a/backend/archive-service/target/classes/com/artedu/archive/service/MockArchiveDataProvider.class b/backend/archive-service/target/classes/com/artedu/archive/service/MockArchiveDataProvider.class new file mode 100644 index 0000000..5450297 Binary files /dev/null and b/backend/archive-service/target/classes/com/artedu/archive/service/MockArchiveDataProvider.class differ diff --git a/backend/archive-service/target/classes/com/artedu/archive/util/RsaDecryptUtil.class b/backend/archive-service/target/classes/com/artedu/archive/util/RsaDecryptUtil.class index 4841909..00fbb85 100644 Binary files a/backend/archive-service/target/classes/com/artedu/archive/util/RsaDecryptUtil.class and b/backend/archive-service/target/classes/com/artedu/archive/util/RsaDecryptUtil.class differ diff --git a/backend/archive-service/target/classes/com/tencent/wework/Finance.class b/backend/archive-service/target/classes/com/tencent/wework/Finance.class new file mode 100644 index 0000000..c326ae2 Binary files /dev/null and b/backend/archive-service/target/classes/com/tencent/wework/Finance.class differ diff --git a/backend/archive-service/target/classes/com/tencent/wework/Finance.java.utf8 b/backend/archive-service/target/classes/com/tencent/wework/Finance.java.utf8 new file mode 100644 index 0000000..e69de29 diff --git a/backend/archive-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/backend/archive-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index 0bcb862..17ddf55 100644 --- a/backend/archive-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/backend/archive-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,8 +1,8 @@ +com\tencent\wework\Finance.class com\artedu\archive\service\ArchivePullService.class com\artedu\archive\controller\ArchiveCallbackController.class -com\artedu\archive\sdk\WeWorkFinanceSdk.class com\artedu\archive\ArchiveServiceApplication.class -com\artedu\archive\sdk\WeWorkFinanceSdk$Slice_t.class +com\artedu\archive\service\MockArchiveDataProvider.class com\artedu\archive\entity\ArchiveMessage.class com\artedu\archive\mapper\ArchiveMessageMapper.class com\artedu\archive\util\RsaDecryptUtil.class diff --git a/backend/archive-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/backend/archive-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index 9b36b11..5536605 100644 --- a/backend/archive-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/backend/archive-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -2,6 +2,7 @@ D:\www\agent_9art\backend\archive-service\src\main\java\com\artedu\archive\Archi D:\www\agent_9art\backend\archive-service\src\main\java\com\artedu\archive\controller\ArchiveCallbackController.java D:\www\agent_9art\backend\archive-service\src\main\java\com\artedu\archive\entity\ArchiveMessage.java D:\www\agent_9art\backend\archive-service\src\main\java\com\artedu\archive\mapper\ArchiveMessageMapper.java -D:\www\agent_9art\backend\archive-service\src\main\java\com\artedu\archive\sdk\WeWorkFinanceSdk.java D:\www\agent_9art\backend\archive-service\src\main\java\com\artedu\archive\service\ArchivePullService.java +D:\www\agent_9art\backend\archive-service\src\main\java\com\artedu\archive\service\MockArchiveDataProvider.java D:\www\agent_9art\backend\archive-service\src\main\java\com\artedu\archive\util\RsaDecryptUtil.java +D:\www\agent_9art\backend\archive-service\src\main\java\com\tencent\wework\Finance.java diff --git a/backend/archive-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/backend/archive-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..6d12f8e --- /dev/null +++ b/backend/archive-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst @@ -0,0 +1,2 @@ +com\artedu\archive\controller\ArchiveCallbackControllerTest.class +com\artedu\archive\service\ArchivePullServiceTest.class diff --git a/backend/archive-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/backend/archive-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..75fc04f --- /dev/null +++ b/backend/archive-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1,2 @@ +D:\www\agent_9art\backend\archive-service\src\test\java\com\artedu\archive\controller\ArchiveCallbackControllerTest.java +D:\www\agent_9art\backend\archive-service\src\test\java\com\artedu\archive\service\ArchivePullServiceTest.java diff --git a/backend/archive-service/target/test-classes/com/artedu/archive/controller/ArchiveCallbackControllerTest.class b/backend/archive-service/target/test-classes/com/artedu/archive/controller/ArchiveCallbackControllerTest.class new file mode 100644 index 0000000..647986f Binary files /dev/null and b/backend/archive-service/target/test-classes/com/artedu/archive/controller/ArchiveCallbackControllerTest.class differ diff --git a/backend/archive-service/target/test-classes/com/artedu/archive/service/ArchivePullServiceTest.class b/backend/archive-service/target/test-classes/com/artedu/archive/service/ArchivePullServiceTest.class new file mode 100644 index 0000000..9ace06f Binary files /dev/null and b/backend/archive-service/target/test-classes/com/artedu/archive/service/ArchivePullServiceTest.class differ diff --git a/backend/auth-service/Dockerfile b/backend/auth-service/Dockerfile index 858c6fb..5f44601 100644 --- a/backend/auth-service/Dockerfile +++ b/backend/auth-service/Dockerfile @@ -1,5 +1,5 @@ -FROM openjdk:17-jdk-slim +FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY target/*.jar app.jar -EXPOSE 8080 +EXPOSE 8081 ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/backend/auth-service/pom.xml b/backend/auth-service/pom.xml index e1d8ff9..4e71aba 100644 --- a/backend/auth-service/pom.xml +++ b/backend/auth-service/pom.xml @@ -40,6 +40,11 @@ lombok true + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/backend/auth-service/src/main/java/com/artedu/auth/controller/AuthController.java b/backend/auth-service/src/main/java/com/artedu/auth/controller/AuthController.java index 5194293..e48e40f 100644 --- a/backend/auth-service/src/main/java/com/artedu/auth/controller/AuthController.java +++ b/backend/auth-service/src/main/java/com/artedu/auth/controller/AuthController.java @@ -7,6 +7,7 @@ import com.artedu.common.result.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.HashMap; @@ -17,6 +18,7 @@ import java.util.Map; * 处理企微OAuth登录和Token刷新 */ @Slf4j +@Validated @RestController @RequestMapping("/api/v1/auth") public class AuthController { @@ -91,14 +93,12 @@ public class AuthController { return Result.success(result); } + @Value("${wecom.corp-id}") + private String corpId; + @GetMapping("/signature") public Result> getJsSdkSignature( - @RequestParam("url") String url, - @RequestHeader("Authorization") String authHeader) { - - String token = authHeader.replace("Bearer ", ""); - io.jsonwebtoken.Claims claims = jwtService.validateToken(token); - String corpId = claims.get("corpId", String.class); + @RequestParam("url") String url) { String ticket = weComOAuthService.getJsApiTicket(); String nonceStr = java.util.UUID.randomUUID().toString().replace("-", ""); diff --git a/backend/auth-service/src/main/resources/application.yml b/backend/auth-service/src/main/resources/application.yml index 068291a..eef5a23 100644 --- a/backend/auth-service/src/main/resources/application.yml +++ b/backend/auth-service/src/main/resources/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8081 spring: @@ -6,7 +11,7 @@ spring: name: auth-service datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false + url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false username: ${MYSQL_USERNAME:root} password: ${MYSQL_PASSWORD:root} redis: @@ -21,12 +26,12 @@ spring: min-idle: 0 wecom: - corp-id: ${WECOM_CORP_ID:} - agent-id: ${WECOM_AGENT_ID:} - secret: ${WECOM_SECRET:} + corp-id: wwd483c2fba24ae30a + agent-id: 1000021 + secret: nU1xezyHyonS9rOJ9Kwo3i5OM9iuWc1TrjXf-rOYZx0 jwt: - secret: ${JWT_SECRET:your-256-bit-secret-key-here-at-least-32-characters} + secret: ${JWT_SECRET:9artedu-ai-assistant-jwt-secret-key-2024} expiration: 86400 mybatis-plus: diff --git a/backend/auth-service/src/test/java/com/artedu/auth/controller/AuthControllerTest.java b/backend/auth-service/src/test/java/com/artedu/auth/controller/AuthControllerTest.java new file mode 100644 index 0000000..ca95b1c --- /dev/null +++ b/backend/auth-service/src/test/java/com/artedu/auth/controller/AuthControllerTest.java @@ -0,0 +1,103 @@ +package com.artedu.auth.controller; + +import com.artedu.auth.service.WeComOAuthService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class AuthControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private WeComOAuthService weComOAuthService; + + @Test + void testGetJsSdkSignature() throws Exception { + when(weComOAuthService.getJsApiTicket()).thenReturn("test_ticket_123"); + + mockMvc.perform(get("/api/v1/auth/signature") + .param("url", "http://localhost:5173/")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.corpId").exists()) + .andExpect(jsonPath("$.data.signature").exists()) + .andExpect(jsonPath("$.data.timestamp").exists()) + .andExpect(jsonPath("$.data.nonceStr").exists()); + } + + @Test + void testGetJsSdkSignatureWithDifferentUrl() throws Exception { + when(weComOAuthService.getJsApiTicket()).thenReturn("test_ticket_456"); + + MvcResult result1 = mockMvc.perform(get("/api/v1/auth/signature") + .param("url", "http://localhost:5173/path1")) + .andReturn(); + + MvcResult result2 = mockMvc.perform(get("/api/v1/auth/signature") + .param("url", "http://localhost:5173/path2")) + .andReturn(); + + String sig1 = result1.getResponse().getContentAsString(); + String sig2 = result2.getResponse().getContentAsString(); + // 不同 URL 应该产生不同签名 + assertNotEquals(sig1, sig2); + } + + @Test + void testGetUserInfoWithValidToken() throws Exception { + // 先用签名接口获取一个正常响应,验证 token 解析需要更复杂的设置 + // 这里简单验证接口存在 + mockMvc.perform(get("/api/v1/auth/signature") + .param("url", "http://test.com")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)); + } + + @Test + void testSignatureDeterminismWithSameNonce() { + // 验证签名算法的确定性:相同输入应产生相同输出 + String ticket = "ticket123"; + String nonce = "nonce456"; + String timestamp = "1234567890"; + String url = "http://test.com"; + + String sig1 = sha1("jsapi_ticket=" + ticket + "&noncestr=" + nonce + "×tamp=" + timestamp + "&url=" + url); + String sig2 = sha1("jsapi_ticket=" + ticket + "&noncestr=" + nonce + "×tamp=" + timestamp + "&url=" + url); + + assertEquals(sig1, sig2); + } + + private String sha1(String input) { + try { + java.security.MessageDigest digest = java.security.MessageDigest.getInstance("SHA1"); + digest.update(input.getBytes()); + byte[] messageDigest = digest.digest(); + StringBuilder hexString = new StringBuilder(); + for (byte b : messageDigest) { + String shaHex = Integer.toHexString(b & 0xFF); + if (shaHex.length() < 2) { + hexString.append(0); + } + hexString.append(shaHex); + } + return hexString.toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/backend/auth-service/target/auth-service-1.0.0-SNAPSHOT.jar b/backend/auth-service/target/auth-service-1.0.0-SNAPSHOT.jar index 15181b3..cf1a109 100644 Binary files a/backend/auth-service/target/auth-service-1.0.0-SNAPSHOT.jar and b/backend/auth-service/target/auth-service-1.0.0-SNAPSHOT.jar differ diff --git a/backend/auth-service/target/auth-service-1.0.0-SNAPSHOT.jar.original b/backend/auth-service/target/auth-service-1.0.0-SNAPSHOT.jar.original new file mode 100644 index 0000000..98b61c6 Binary files /dev/null and b/backend/auth-service/target/auth-service-1.0.0-SNAPSHOT.jar.original differ diff --git a/backend/auth-service/target/classes/application.yml b/backend/auth-service/target/classes/application.yml index 03a0254..eef5a23 100644 --- a/backend/auth-service/target/classes/application.yml +++ b/backend/auth-service/target/classes/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8081 spring: @@ -21,12 +26,12 @@ spring: min-idle: 0 wecom: - corp-id: ${WECOM_CORP_ID:} - agent-id: ${WECOM_AGENT_ID:} - secret: ${WECOM_SECRET:} + corp-id: wwd483c2fba24ae30a + agent-id: 1000021 + secret: nU1xezyHyonS9rOJ9Kwo3i5OM9iuWc1TrjXf-rOYZx0 jwt: - secret: ${JWT_SECRET:your-256-bit-secret-key-here-at-least-32-characters} + secret: ${JWT_SECRET:9artedu-ai-assistant-jwt-secret-key-2024} expiration: 86400 mybatis-plus: diff --git a/backend/auth-service/target/classes/com/artedu/auth/AuthServiceApplication.class b/backend/auth-service/target/classes/com/artedu/auth/AuthServiceApplication.class index a31f359..56547e5 100644 Binary files a/backend/auth-service/target/classes/com/artedu/auth/AuthServiceApplication.class and b/backend/auth-service/target/classes/com/artedu/auth/AuthServiceApplication.class differ diff --git a/backend/auth-service/target/classes/com/artedu/auth/controller/AuthController.class b/backend/auth-service/target/classes/com/artedu/auth/controller/AuthController.class index bde7d86..3875a64 100644 Binary files a/backend/auth-service/target/classes/com/artedu/auth/controller/AuthController.class and b/backend/auth-service/target/classes/com/artedu/auth/controller/AuthController.class differ diff --git a/backend/auth-service/target/classes/com/artedu/auth/entity/Staff.class b/backend/auth-service/target/classes/com/artedu/auth/entity/Staff.class index df6db07..272bebd 100644 Binary files a/backend/auth-service/target/classes/com/artedu/auth/entity/Staff.class and b/backend/auth-service/target/classes/com/artedu/auth/entity/Staff.class differ diff --git a/backend/auth-service/target/classes/com/artedu/auth/mapper/StaffMapper.class b/backend/auth-service/target/classes/com/artedu/auth/mapper/StaffMapper.class index 87dba69..c960773 100644 Binary files a/backend/auth-service/target/classes/com/artedu/auth/mapper/StaffMapper.class and b/backend/auth-service/target/classes/com/artedu/auth/mapper/StaffMapper.class differ diff --git a/backend/auth-service/target/classes/com/artedu/auth/service/JwtService.class b/backend/auth-service/target/classes/com/artedu/auth/service/JwtService.class index f8ebcef..dd1cd22 100644 Binary files a/backend/auth-service/target/classes/com/artedu/auth/service/JwtService.class and b/backend/auth-service/target/classes/com/artedu/auth/service/JwtService.class differ diff --git a/backend/auth-service/target/classes/com/artedu/auth/service/WeComOAuthService.class b/backend/auth-service/target/classes/com/artedu/auth/service/WeComOAuthService.class index 2f2a6b3..2a53443 100644 Binary files a/backend/auth-service/target/classes/com/artedu/auth/service/WeComOAuthService.class and b/backend/auth-service/target/classes/com/artedu/auth/service/WeComOAuthService.class differ diff --git a/backend/auth-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/backend/auth-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..e8371bd --- /dev/null +++ b/backend/auth-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst @@ -0,0 +1 @@ +com\artedu\auth\controller\AuthControllerTest.class diff --git a/backend/auth-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/backend/auth-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..f8f06a9 --- /dev/null +++ b/backend/auth-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1 @@ +D:\www\agent_9art\backend\auth-service\src\test\java\com\artedu\auth\controller\AuthControllerTest.java diff --git a/backend/auth-service/target/test-classes/com/artedu/auth/controller/AuthControllerTest.class b/backend/auth-service/target/test-classes/com/artedu/auth/controller/AuthControllerTest.class new file mode 100644 index 0000000..ad4de91 Binary files /dev/null and b/backend/auth-service/target/test-classes/com/artedu/auth/controller/AuthControllerTest.class differ diff --git a/backend/common/src/main/java/com/artedu/common/exception/GlobalExceptionHandler.java b/backend/common/src/main/java/com/artedu/common/exception/GlobalExceptionHandler.java index ff0f5c4..e8fd219 100644 --- a/backend/common/src/main/java/com/artedu/common/exception/GlobalExceptionHandler.java +++ b/backend/common/src/main/java/com/artedu/common/exception/GlobalExceptionHandler.java @@ -3,9 +3,15 @@ package com.artedu.common.exception; import com.artedu.common.result.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.BindException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import java.util.stream.Collectors; + /** * 全局异常处理器 * 统一处理控制器层抛出的异常,转换为标准响应格式 @@ -20,6 +26,30 @@ public class GlobalExceptionHandler { return Result.fail(e.getCode(), e.getMessage()); } + /** + * 处理 @RequestBody + @Valid 校验失败 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Result handleMethodArgumentNotValid(MethodArgumentNotValidException e) { + String message = e.getBindingResult().getFieldErrors().stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .collect(Collectors.joining("; ")); + log.warn("参数校验失败: {}", message); + return Result.fail(400, message); + } + + /** + * 处理 @RequestParam/@PathVariable + @Valid 校验失败 + */ + @ExceptionHandler(ConstraintViolationException.class) + public Result handleConstraintViolation(ConstraintViolationException e) { + String message = e.getConstraintViolations().stream() + .map(ConstraintViolation::getMessage) + .collect(Collectors.joining("; ")); + log.warn("参数校验失败: {}", message); + return Result.fail(400, message); + } + @ExceptionHandler(BindException.class) public Result handleBindException(BindException e) { String message = e.getBindingResult().getFieldErrors().stream() @@ -30,6 +60,18 @@ public class GlobalExceptionHandler { return Result.fail(400, message); } + @ExceptionHandler(IllegalArgumentException.class) + public Result handleIllegalArgument(IllegalArgumentException e) { + log.warn("非法参数: {}", e.getMessage()); + return Result.fail(400, e.getMessage()); + } + + @ExceptionHandler(MissingServletRequestParameterException.class) + public Result handleMissingParam(MissingServletRequestParameterException e) { + log.warn("缺少参数: {}", e.getMessage()); + return Result.fail(400, "参数不能为空: " + e.getParameterName()); + } + @ExceptionHandler(Exception.class) public Result handleException(Exception e) { log.error("系统异常: {}", e.getMessage(), e); diff --git a/backend/common/target/classes/com/artedu/common/config/CorsConfig.class b/backend/common/target/classes/com/artedu/common/config/CorsConfig.class index b255aa0..7011286 100644 Binary files a/backend/common/target/classes/com/artedu/common/config/CorsConfig.class and b/backend/common/target/classes/com/artedu/common/config/CorsConfig.class differ diff --git a/backend/common/target/classes/com/artedu/common/config/MyBatisPlusConfig.class b/backend/common/target/classes/com/artedu/common/config/MyBatisPlusConfig.class index 8ebb148..12f85ae 100644 Binary files a/backend/common/target/classes/com/artedu/common/config/MyBatisPlusConfig.class and b/backend/common/target/classes/com/artedu/common/config/MyBatisPlusConfig.class differ diff --git a/backend/common/target/classes/com/artedu/common/config/RabbitConfig.class b/backend/common/target/classes/com/artedu/common/config/RabbitConfig.class index de4a86a..f32426a 100644 Binary files a/backend/common/target/classes/com/artedu/common/config/RabbitConfig.class and b/backend/common/target/classes/com/artedu/common/config/RabbitConfig.class differ diff --git a/backend/common/target/classes/com/artedu/common/config/RedisConfig.class b/backend/common/target/classes/com/artedu/common/config/RedisConfig.class index bcfb183..ca104a8 100644 Binary files a/backend/common/target/classes/com/artedu/common/config/RedisConfig.class and b/backend/common/target/classes/com/artedu/common/config/RedisConfig.class differ diff --git a/backend/common/target/classes/com/artedu/common/entity/BaseEntity.class b/backend/common/target/classes/com/artedu/common/entity/BaseEntity.class index d9e1e9f..5c0c181 100644 Binary files a/backend/common/target/classes/com/artedu/common/entity/BaseEntity.class and b/backend/common/target/classes/com/artedu/common/entity/BaseEntity.class differ diff --git a/backend/common/target/classes/com/artedu/common/exception/BusinessException.class b/backend/common/target/classes/com/artedu/common/exception/BusinessException.class index b2f25b0..a53acc4 100644 Binary files a/backend/common/target/classes/com/artedu/common/exception/BusinessException.class and b/backend/common/target/classes/com/artedu/common/exception/BusinessException.class differ diff --git a/backend/common/target/classes/com/artedu/common/exception/GlobalExceptionHandler.class b/backend/common/target/classes/com/artedu/common/exception/GlobalExceptionHandler.class index 574e3df..32efa25 100644 Binary files a/backend/common/target/classes/com/artedu/common/exception/GlobalExceptionHandler.class and b/backend/common/target/classes/com/artedu/common/exception/GlobalExceptionHandler.class differ diff --git a/backend/common/target/classes/com/artedu/common/handler/AutoFillHandler.class b/backend/common/target/classes/com/artedu/common/handler/AutoFillHandler.class index 0edcf84..d24ff82 100644 Binary files a/backend/common/target/classes/com/artedu/common/handler/AutoFillHandler.class and b/backend/common/target/classes/com/artedu/common/handler/AutoFillHandler.class differ diff --git a/backend/common/target/classes/com/artedu/common/mybatis/JsonTypeHandler.class b/backend/common/target/classes/com/artedu/common/mybatis/JsonTypeHandler.class index 4b2ee12..dca2af2 100644 Binary files a/backend/common/target/classes/com/artedu/common/mybatis/JsonTypeHandler.class and b/backend/common/target/classes/com/artedu/common/mybatis/JsonTypeHandler.class differ diff --git a/backend/common/target/classes/com/artedu/common/result/PageResult.class b/backend/common/target/classes/com/artedu/common/result/PageResult.class index 053ab35..4db5a50 100644 Binary files a/backend/common/target/classes/com/artedu/common/result/PageResult.class and b/backend/common/target/classes/com/artedu/common/result/PageResult.class differ diff --git a/backend/common/target/classes/com/artedu/common/result/Result.class b/backend/common/target/classes/com/artedu/common/result/Result.class index cc62b9b..2b7ab51 100644 Binary files a/backend/common/target/classes/com/artedu/common/result/Result.class and b/backend/common/target/classes/com/artedu/common/result/Result.class differ diff --git a/backend/common/target/classes/com/artedu/common/util/JsonUtils$1.class b/backend/common/target/classes/com/artedu/common/util/JsonUtils$1.class index 08048c4..ebfbd27 100644 Binary files a/backend/common/target/classes/com/artedu/common/util/JsonUtils$1.class and b/backend/common/target/classes/com/artedu/common/util/JsonUtils$1.class differ diff --git a/backend/common/target/classes/com/artedu/common/util/JsonUtils.class b/backend/common/target/classes/com/artedu/common/util/JsonUtils.class index 940f6ae..1a0e4a0 100644 Binary files a/backend/common/target/classes/com/artedu/common/util/JsonUtils.class and b/backend/common/target/classes/com/artedu/common/util/JsonUtils.class differ diff --git a/backend/common/target/common-1.0.0-SNAPSHOT.jar b/backend/common/target/common-1.0.0-SNAPSHOT.jar index 640cb2a..0fa4dd5 100644 Binary files a/backend/common/target/common-1.0.0-SNAPSHOT.jar and b/backend/common/target/common-1.0.0-SNAPSHOT.jar differ diff --git a/backend/conversation-service/Dockerfile b/backend/conversation-service/Dockerfile index 858c6fb..9aeb920 100644 --- a/backend/conversation-service/Dockerfile +++ b/backend/conversation-service/Dockerfile @@ -1,5 +1,5 @@ -FROM openjdk:17-jdk-slim +FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY target/*.jar app.jar -EXPOSE 8080 +EXPOSE 8083 ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/backend/conversation-service/pom.xml b/backend/conversation-service/pom.xml index ea7f67f..d3a970c 100644 --- a/backend/conversation-service/pom.xml +++ b/backend/conversation-service/pom.xml @@ -15,6 +15,7 @@ com.arteducommon${project.version} org.springframework.bootspring-boot-starter-web org.springframework.bootspring-boot-starter-websocket + org.springframework.bootspring-boot-starter-testtest diff --git a/backend/conversation-service/src/main/java/com/artedu/conversation/controller/ConversationController.java b/backend/conversation-service/src/main/java/com/artedu/conversation/controller/ConversationController.java index 2c3aa9e..2a32486 100644 --- a/backend/conversation-service/src/main/java/com/artedu/conversation/controller/ConversationController.java +++ b/backend/conversation-service/src/main/java/com/artedu/conversation/controller/ConversationController.java @@ -2,15 +2,22 @@ package com.artedu.conversation.controller; import com.artedu.common.result.Result; import com.artedu.conversation.entity.Conversation; +import com.artedu.conversation.entity.ConversationTurn; import com.artedu.conversation.service.ConversationManager; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import javax.validation.constraints.NotBlank; import java.util.HashMap; +import java.util.List; import java.util.Map; @Slf4j +@Validated @RestController @RequestMapping("/api/v1/conversations") public class ConversationController { @@ -19,7 +26,7 @@ public class ConversationController { private ConversationManager conversationManager; @GetMapping("/{sessionId}/context") - public Result> getContext(@PathVariable("sessionId") String sessionId) { + public Result> getContext(@NotBlank(message = "sessionId不能为空") @PathVariable("sessionId") String sessionId) { String contextStr = conversationManager.getContextString(sessionId); var messages = conversationManager.getContextMessages(sessionId); @@ -33,14 +40,29 @@ public class ConversationController { } @GetMapping("/{sessionId}") - public Result getConversation(@PathVariable("sessionId") String sessionId) { - return Result.success(); + public Result getConversation(@NotBlank(message = "sessionId不能为空") @PathVariable("sessionId") String sessionId) { + Conversation conv = conversationManager.getSession(sessionId); + return Result.success(conv); + } + + @GetMapping("/{sessionId}/turns") + public Result> getTurns(@NotBlank(message = "sessionId不能为空") @PathVariable("sessionId") String sessionId) { + List turns = conversationManager.getTurns(sessionId); + return Result.success(turns); + } + + @GetMapping("") + public Result> listByCustomer( + @NotBlank(message = "customerId不能为空") @RequestParam("customerId") String customerId, + @NotBlank(message = "corpId不能为空") @RequestParam("corpId") String corpId) { + List list = conversationManager.listByCustomer(customerId, corpId); + return Result.success(list); } @PutMapping("/{sessionId}/status") public Result updateStatus( - @PathVariable("sessionId") String sessionId, - @RequestParam("stage") String stage, + @NotBlank(message = "sessionId不能为空") @PathVariable("sessionId") String sessionId, + @NotBlank(message = "stage不能为空") @RequestParam("stage") String stage, @RequestParam(value = "confidence", required = false) Double confidence, @RequestParam(value = "summary", required = false) String summary) { diff --git a/backend/conversation-service/src/main/java/com/artedu/conversation/service/ConversationManager.java b/backend/conversation-service/src/main/java/com/artedu/conversation/service/ConversationManager.java index 5cff9b5..b096597 100644 --- a/backend/conversation-service/src/main/java/com/artedu/conversation/service/ConversationManager.java +++ b/backend/conversation-service/src/main/java/com/artedu/conversation/service/ConversationManager.java @@ -33,6 +33,31 @@ public class ConversationManager { private static final int MAX_CONTEXT_ROUNDS = 10; private static final int CONTEXT_EXPIRE_MINUTES = 30; + public Conversation getSession(String sessionId) { + return conversationMapper.selectOne( + new LambdaQueryWrapper() + .eq(Conversation::getSessionId, sessionId) + ); + } + + public List getTurns(String sessionId) { + return conversationTurnMapper.selectList( + new LambdaQueryWrapper() + .eq(ConversationTurn::getSessionId, sessionId) + .orderByAsc(ConversationTurn::getTurnNumber) + ); + } + + public List listByCustomer(String customerId, String corpId) { + return conversationMapper.selectList( + new LambdaQueryWrapper() + .eq(Conversation::getCustomerId, customerId) + .eq(Conversation::getCorpId, corpId) + .orderByDesc(Conversation::getStartTime) + .last("LIMIT 10") + ); + } + public Conversation getOrCreateSession(String sessionId, String corpId, String customerId, String staffId) { Conversation conv = conversationMapper.selectOne( diff --git a/backend/conversation-service/src/main/resources/application.yml b/backend/conversation-service/src/main/resources/application.yml index 5ab17b3..5264743 100644 --- a/backend/conversation-service/src/main/resources/application.yml +++ b/backend/conversation-service/src/main/resources/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8083 spring: @@ -6,7 +11,7 @@ spring: name: conversation-service datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false + url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false username: ${MYSQL_USERNAME:root} password: ${MYSQL_PASSWORD:root} redis: diff --git a/backend/conversation-service/src/test/java/com/artedu/conversation/controller/ConversationControllerTest.java b/backend/conversation-service/src/test/java/com/artedu/conversation/controller/ConversationControllerTest.java new file mode 100644 index 0000000..aa22139 --- /dev/null +++ b/backend/conversation-service/src/test/java/com/artedu/conversation/controller/ConversationControllerTest.java @@ -0,0 +1,143 @@ +package com.artedu.conversation.controller; + +import com.artedu.conversation.entity.Conversation; +import com.artedu.conversation.entity.ConversationTurn; +import com.artedu.conversation.service.ConversationManager; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(properties = { + "spring.data.redis.repositories.enabled=false", + "spring.rabbitmq.listener.simple.auto-startup=false" +}) +@AutoConfigureMockMvc +public class ConversationControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private ConversationManager conversationManager; + + @Test + void testGetConversation() throws Exception { + Conversation conv = new Conversation(); + conv.setSessionId("sess_001"); + conv.setCustomerId("wx_001"); + conv.setCurrentStage("STAGE-03"); + conv.setRoundCount(5); + + when(conversationManager.getSession("sess_001")).thenReturn(conv); + + mockMvc.perform(get("/api/v1/conversations/sess_001")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.sessionId").value("sess_001")) + .andExpect(jsonPath("$.data.roundCount").value(5)); + } + + @Test + void testGetTurns() throws Exception { + ConversationTurn turn1 = new ConversationTurn(); + turn1.setTurnNumber(1); + turn1.setStudentContent("学员消息1"); + turn1.setSeatContent("顾问回复1"); + + ConversationTurn turn2 = new ConversationTurn(); + turn2.setTurnNumber(2); + turn2.setStudentContent("学员消息2"); + turn2.setSeatContent("顾问回复2"); + + when(conversationManager.getTurns("sess_001")) + .thenReturn(List.of(turn1, turn2)); + + mockMvc.perform(get("/api/v1/conversations/sess_001/turns")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.length()").value(2)) + .andExpect(jsonPath("$.data[0].studentContent").value("学员消息1")); + } + + @Test + void testListByCustomer() throws Exception { + Conversation conv1 = new Conversation(); + conv1.setSessionId("sess_001"); + conv1.setCustomerId("wx_001"); + + Conversation conv2 = new Conversation(); + conv2.setSessionId("sess_002"); + conv2.setCustomerId("wx_001"); + + when(conversationManager.listByCustomer("wx_001", "corp001")) + .thenReturn(List.of(conv1, conv2)); + + mockMvc.perform(get("/api/v1/conversations") + .param("customerId", "wx_001") + .param("corpId", "corp001")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.length()").value(2)); + } + + @Test + void testUpdateStatus() throws Exception { + doNothing().when(conversationManager) + .updateConversationStatus("sess_001", "STAGE-04", 0.95, "摘要"); + + mockMvc.perform(put("/api/v1/conversations/sess_001/status") + .param("stage", "STAGE-04") + .param("confidence", "0.95") + .param("summary", "摘要")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").value("状态更新成功")); + } + + @Test + void testGetContext() throws Exception { + when(conversationManager.getContextString("sess_001")).thenReturn("学员: 你好"); + when(conversationManager.getContextMessages("sess_001")).thenReturn(List.of()); + + mockMvc.perform(get("/api/v1/conversations/sess_001/context")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.context").value("学员: 你好")); + } + + @Test + void testGetConversationBlankSessionId_shouldReturn400() throws Exception { + mockMvc.perform(get("/api/v1/conversations/ ")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(400)) + .andExpect(jsonPath("$.message").exists()); + } + + @Test + void testListByCustomerMissingCustomerId_shouldReturn400() throws Exception { + mockMvc.perform(get("/api/v1/conversations") + .param("corpId", "corp001")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(400)) + .andExpect(jsonPath("$.message").exists()); + } + + @Test + void testUpdateStatusMissingStage_shouldReturn400() throws Exception { + mockMvc.perform(put("/api/v1/conversations/sess_001/status")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(400)) + .andExpect(jsonPath("$.message").exists()); + } +} diff --git a/backend/conversation-service/target/classes/application.yml b/backend/conversation-service/target/classes/application.yml index 6fc445b..5264743 100644 --- a/backend/conversation-service/target/classes/application.yml +++ b/backend/conversation-service/target/classes/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8083 spring: diff --git a/backend/conversation-service/target/classes/com/artedu/conversation/ConversationServiceApplication.class b/backend/conversation-service/target/classes/com/artedu/conversation/ConversationServiceApplication.class index c8f43d7..5491d70 100644 Binary files a/backend/conversation-service/target/classes/com/artedu/conversation/ConversationServiceApplication.class and b/backend/conversation-service/target/classes/com/artedu/conversation/ConversationServiceApplication.class differ diff --git a/backend/conversation-service/target/classes/com/artedu/conversation/controller/ConversationController.class b/backend/conversation-service/target/classes/com/artedu/conversation/controller/ConversationController.class index 65d7d1b..7e321d3 100644 Binary files a/backend/conversation-service/target/classes/com/artedu/conversation/controller/ConversationController.class and b/backend/conversation-service/target/classes/com/artedu/conversation/controller/ConversationController.class differ diff --git a/backend/conversation-service/target/classes/com/artedu/conversation/entity/Conversation.class b/backend/conversation-service/target/classes/com/artedu/conversation/entity/Conversation.class index ec2dde4..730a87f 100644 Binary files a/backend/conversation-service/target/classes/com/artedu/conversation/entity/Conversation.class and b/backend/conversation-service/target/classes/com/artedu/conversation/entity/Conversation.class differ diff --git a/backend/conversation-service/target/classes/com/artedu/conversation/entity/ConversationTurn.class b/backend/conversation-service/target/classes/com/artedu/conversation/entity/ConversationTurn.class index e1d5a47..9cdba55 100644 Binary files a/backend/conversation-service/target/classes/com/artedu/conversation/entity/ConversationTurn.class and b/backend/conversation-service/target/classes/com/artedu/conversation/entity/ConversationTurn.class differ diff --git a/backend/conversation-service/target/classes/com/artedu/conversation/mapper/ConversationMapper.class b/backend/conversation-service/target/classes/com/artedu/conversation/mapper/ConversationMapper.class index 1f7764f..ce17d44 100644 Binary files a/backend/conversation-service/target/classes/com/artedu/conversation/mapper/ConversationMapper.class and b/backend/conversation-service/target/classes/com/artedu/conversation/mapper/ConversationMapper.class differ diff --git a/backend/conversation-service/target/classes/com/artedu/conversation/mapper/ConversationTurnMapper.class b/backend/conversation-service/target/classes/com/artedu/conversation/mapper/ConversationTurnMapper.class index 4ecef07..c66c71f 100644 Binary files a/backend/conversation-service/target/classes/com/artedu/conversation/mapper/ConversationTurnMapper.class and b/backend/conversation-service/target/classes/com/artedu/conversation/mapper/ConversationTurnMapper.class differ diff --git a/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager$ArchiveMessageEvent.class b/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager$ArchiveMessageEvent.class index 925e1ce..c17e2ed 100644 Binary files a/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager$ArchiveMessageEvent.class and b/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager$ArchiveMessageEvent.class differ diff --git a/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager$ContextMessage.class b/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager$ContextMessage.class index dd679a5..2eff885 100644 Binary files a/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager$ContextMessage.class and b/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager$ContextMessage.class differ diff --git a/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager.class b/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager.class index 2e6f438..3e55fcc 100644 Binary files a/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager.class and b/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager.class differ diff --git a/backend/conversation-service/target/conversation-service-1.0.0-SNAPSHOT.jar b/backend/conversation-service/target/conversation-service-1.0.0-SNAPSHOT.jar index cca3e91..c53a147 100644 Binary files a/backend/conversation-service/target/conversation-service-1.0.0-SNAPSHOT.jar and b/backend/conversation-service/target/conversation-service-1.0.0-SNAPSHOT.jar differ diff --git a/backend/conversation-service/target/conversation-service-1.0.0-SNAPSHOT.jar.original b/backend/conversation-service/target/conversation-service-1.0.0-SNAPSHOT.jar.original new file mode 100644 index 0000000..40a2693 Binary files /dev/null and b/backend/conversation-service/target/conversation-service-1.0.0-SNAPSHOT.jar.original differ diff --git a/backend/conversation-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/backend/conversation-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..8eb6244 --- /dev/null +++ b/backend/conversation-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst @@ -0,0 +1 @@ +com\artedu\conversation\controller\ConversationControllerTest.class diff --git a/backend/conversation-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/backend/conversation-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..40e5021 --- /dev/null +++ b/backend/conversation-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1 @@ +D:\www\agent_9art\backend\conversation-service\src\test\java\com\artedu\conversation\controller\ConversationControllerTest.java diff --git a/backend/conversation-service/target/test-classes/com/artedu/conversation/controller/ConversationControllerTest.class b/backend/conversation-service/target/test-classes/com/artedu/conversation/controller/ConversationControllerTest.class new file mode 100644 index 0000000..b2e4409 Binary files /dev/null and b/backend/conversation-service/target/test-classes/com/artedu/conversation/controller/ConversationControllerTest.class differ diff --git a/backend/gateway/Dockerfile b/backend/gateway/Dockerfile index 858c6fb..0d438cb 100644 --- a/backend/gateway/Dockerfile +++ b/backend/gateway/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17-jdk-slim +FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY target/*.jar app.jar EXPOSE 8080 diff --git a/backend/gateway/src/main/resources/application.yml b/backend/gateway/src/main/resources/application.yml index e594c69..de92f68 100644 --- a/backend/gateway/src/main/resources/application.yml +++ b/backend/gateway/src/main/resources/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8080 spring: @@ -8,35 +13,35 @@ spring: gateway: routes: - id: auth-service - uri: http://localhost:8081 + uri: http://auth-service:8081 predicates: - Path=/api/v1/auth/** - id: archive-service - uri: http://localhost:8082 + uri: http://archive-service:8082 predicates: - Path=/api/v1/archive/** - id: conversation-service - uri: http://localhost:8083 + uri: http://conversation-service:8083 predicates: - Path=/api/v1/conversations/** - id: intent-service - uri: http://localhost:8084 + uri: http://intent-service:8084 predicates: - Path=/api/v1/intent/** - id: recommend-service - uri: http://localhost:8085 + uri: http://recommend-service:8085 predicates: - Path=/api/v1/recommend/** - id: generation-service - uri: http://localhost:8086 + uri: http://generation-service:8086 predicates: - Path=/api/v1/generation/** - id: kb-admin-service - uri: http://localhost:8087 + uri: http://kb-admin-service:8087 predicates: - Path=/api/v1/admin/** - id: analytics-service - uri: http://localhost:8088 + uri: http://analytics-service:8088 predicates: - Path=/api/v1/analytics/** globalcors: diff --git a/backend/gateway/target/classes/application.yml b/backend/gateway/target/classes/application.yml index 8f95182..de92f68 100644 --- a/backend/gateway/target/classes/application.yml +++ b/backend/gateway/target/classes/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8080 spring: @@ -8,41 +13,41 @@ spring: gateway: routes: - id: auth-service - uri: http://localhost:8081 + uri: http://auth-service:8081 predicates: - Path=/api/v1/auth/** - id: archive-service - uri: http://localhost:8082 + uri: http://archive-service:8082 predicates: - Path=/api/v1/archive/** - id: conversation-service - uri: http://localhost:8083 + uri: http://conversation-service:8083 predicates: - Path=/api/v1/conversations/** - id: intent-service - uri: http://localhost:8084 + uri: http://intent-service:8084 predicates: - Path=/api/v1/intent/** - id: recommend-service - uri: http://localhost:8085 + uri: http://recommend-service:8085 predicates: - Path=/api/v1/recommend/** - id: generation-service - uri: http://localhost:8086 + uri: http://generation-service:8086 predicates: - Path=/api/v1/generation/** - id: kb-admin-service - uri: http://localhost:8087 + uri: http://kb-admin-service:8087 predicates: - Path=/api/v1/admin/** - id: analytics-service - uri: http://localhost:8088 + uri: http://analytics-service:8088 predicates: - Path=/api/v1/analytics/** globalcors: cors-configurations: '[/**]': - allowedOrigins: "*" + allowedOriginPatterns: "*" allowedMethods: "*" allowedHeaders: "*" allowCredentials: true diff --git a/backend/gateway/target/classes/com/artedu/gateway/GatewayApplication.class b/backend/gateway/target/classes/com/artedu/gateway/GatewayApplication.class index 0b9a906..3b616b3 100644 Binary files a/backend/gateway/target/classes/com/artedu/gateway/GatewayApplication.class and b/backend/gateway/target/classes/com/artedu/gateway/GatewayApplication.class differ diff --git a/backend/gateway/target/gateway-1.0.0-SNAPSHOT.jar b/backend/gateway/target/gateway-1.0.0-SNAPSHOT.jar index 0ec0e3d..e1ad64e 100644 Binary files a/backend/gateway/target/gateway-1.0.0-SNAPSHOT.jar and b/backend/gateway/target/gateway-1.0.0-SNAPSHOT.jar differ diff --git a/backend/gateway/target/gateway-1.0.0-SNAPSHOT.jar.original b/backend/gateway/target/gateway-1.0.0-SNAPSHOT.jar.original new file mode 100644 index 0000000..c444312 Binary files /dev/null and b/backend/gateway/target/gateway-1.0.0-SNAPSHOT.jar.original differ diff --git a/backend/generation-service/Dockerfile b/backend/generation-service/Dockerfile index 858c6fb..0ecfa1c 100644 --- a/backend/generation-service/Dockerfile +++ b/backend/generation-service/Dockerfile @@ -1,5 +1,5 @@ -FROM openjdk:17-jdk-slim +FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY target/*.jar app.jar -EXPOSE 8080 +EXPOSE 8086 ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/backend/generation-service/src/main/java/com/artedu/generation/controller/GenerationController.java b/backend/generation-service/src/main/java/com/artedu/generation/controller/GenerationController.java index 33fe8b9..7b2e366 100644 --- a/backend/generation-service/src/main/java/com/artedu/generation/controller/GenerationController.java +++ b/backend/generation-service/src/main/java/com/artedu/generation/controller/GenerationController.java @@ -5,12 +5,15 @@ import com.artedu.generation.service.PromptEngine; import com.artedu.generation.service.QianwenClient; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import javax.validation.constraints.NotBlank; import java.util.HashMap; import java.util.Map; @Slf4j +@Validated @RestController @RequestMapping("/api/v1/generation") public class GenerationController { @@ -23,10 +26,10 @@ public class GenerationController { @PostMapping("/utterance") public Result> generateUtterance( - @RequestParam("message") String message, + @NotBlank(message = "message不能为空") @RequestParam("message") String message, @RequestParam(value = "context", required = false) String context, - @RequestParam("intent") String intent, - @RequestParam("stage") String stage) { + @NotBlank(message = "intent不能为空") @RequestParam("intent") String intent, + @NotBlank(message = "stage不能为空") @RequestParam("stage") String stage) { String prompt = promptEngine.buildUtterancePrompt(message, context, intent, stage); String generated = qianwenClient.generate(prompt, 500); @@ -41,7 +44,7 @@ public class GenerationController { @PostMapping("/summary") public Result> generateSummary( - @RequestParam("context") String context) { + @NotBlank(message = "context不能为空") @RequestParam("context") String context) { String prompt = promptEngine.buildSummaryPrompt(context); String generated = qianwenClient.generate(prompt, 100); diff --git a/backend/generation-service/src/main/resources/application.yml b/backend/generation-service/src/main/resources/application.yml index fca4899..ab9a63e 100644 --- a/backend/generation-service/src/main/resources/application.yml +++ b/backend/generation-service/src/main/resources/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8086 spring: @@ -6,7 +11,7 @@ spring: name: generation-service datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false + url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false username: ${MYSQL_USERNAME:root} password: ${MYSQL_PASSWORD:root} redis: @@ -25,6 +30,11 @@ spring: username: ${RABBITMQ_USERNAME:admin} password: ${RABBITMQ_PASSWORD:admin} +llm: + qianwen: + api-key: sk-39ceb8d746014b349109e76f893acb18 + model: qwen-turbo + mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl diff --git a/backend/generation-service/target/classes/application.yml b/backend/generation-service/target/classes/application.yml index 6d9215d..ab9a63e 100644 --- a/backend/generation-service/target/classes/application.yml +++ b/backend/generation-service/target/classes/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8086 spring: @@ -25,6 +30,11 @@ spring: username: ${RABBITMQ_USERNAME:admin} password: ${RABBITMQ_PASSWORD:admin} +llm: + qianwen: + api-key: sk-39ceb8d746014b349109e76f893acb18 + model: qwen-turbo + mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl diff --git a/backend/generation-service/target/classes/com/artedu/generation/GenerationServiceApplication.class b/backend/generation-service/target/classes/com/artedu/generation/GenerationServiceApplication.class index d89ec9f..7d04df2 100644 Binary files a/backend/generation-service/target/classes/com/artedu/generation/GenerationServiceApplication.class and b/backend/generation-service/target/classes/com/artedu/generation/GenerationServiceApplication.class differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/controller/GenerationController.class b/backend/generation-service/target/classes/com/artedu/generation/controller/GenerationController.class index abd8ea3..5641a84 100644 Binary files a/backend/generation-service/target/classes/com/artedu/generation/controller/GenerationController.class and b/backend/generation-service/target/classes/com/artedu/generation/controller/GenerationController.class differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/service/PromptEngine.class b/backend/generation-service/target/classes/com/artedu/generation/service/PromptEngine.class index f78ed6d..163b8c4 100644 Binary files a/backend/generation-service/target/classes/com/artedu/generation/service/PromptEngine.class and b/backend/generation-service/target/classes/com/artedu/generation/service/PromptEngine.class differ diff --git a/backend/generation-service/target/classes/com/artedu/generation/service/QianwenClient.class b/backend/generation-service/target/classes/com/artedu/generation/service/QianwenClient.class index 070d674..eb55ae2 100644 Binary files a/backend/generation-service/target/classes/com/artedu/generation/service/QianwenClient.class and b/backend/generation-service/target/classes/com/artedu/generation/service/QianwenClient.class differ diff --git a/backend/generation-service/target/generation-service-1.0.0-SNAPSHOT.jar b/backend/generation-service/target/generation-service-1.0.0-SNAPSHOT.jar index 44b4e68..808b8bb 100644 Binary files a/backend/generation-service/target/generation-service-1.0.0-SNAPSHOT.jar and b/backend/generation-service/target/generation-service-1.0.0-SNAPSHOT.jar differ diff --git a/backend/generation-service/target/generation-service-1.0.0-SNAPSHOT.jar.original b/backend/generation-service/target/generation-service-1.0.0-SNAPSHOT.jar.original new file mode 100644 index 0000000..cd2286b Binary files /dev/null and b/backend/generation-service/target/generation-service-1.0.0-SNAPSHOT.jar.original differ diff --git a/backend/intent-service/Dockerfile b/backend/intent-service/Dockerfile index 858c6fb..1a27dd3 100644 --- a/backend/intent-service/Dockerfile +++ b/backend/intent-service/Dockerfile @@ -1,5 +1,5 @@ -FROM openjdk:17-jdk-slim +FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY target/*.jar app.jar -EXPOSE 8080 +EXPOSE 8084 ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/backend/intent-service/pom.xml b/backend/intent-service/pom.xml index c36a227..1059905 100644 --- a/backend/intent-service/pom.xml +++ b/backend/intent-service/pom.xml @@ -14,6 +14,7 @@ com.arteducommon${project.version} org.springframework.bootspring-boot-starter-web + org.springframework.bootspring-boot-starter-testtest diff --git a/backend/intent-service/src/main/java/com/artedu/intent/controller/IntentController.java b/backend/intent-service/src/main/java/com/artedu/intent/controller/IntentController.java index 2087063..6347fe4 100644 --- a/backend/intent-service/src/main/java/com/artedu/intent/controller/IntentController.java +++ b/backend/intent-service/src/main/java/com/artedu/intent/controller/IntentController.java @@ -10,11 +10,14 @@ import com.artedu.intent.service.IntentRecognitionService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import javax.validation.constraints.NotBlank; import java.util.List; @Slf4j +@Validated @RestController @RequestMapping("/api/v1/intent") public class IntentController { @@ -30,10 +33,10 @@ public class IntentController { @PostMapping("/recognize") public Result recognize( - @RequestParam("message") String message, - @RequestParam("customerId") String customerId, - @RequestParam("corpId") String corpId, - @RequestParam("staffId") String staffId, + @NotBlank(message = "message不能为空") @RequestParam("message") String message, + @NotBlank(message = "customerId不能为空") @RequestParam("customerId") String customerId, + @NotBlank(message = "corpId不能为空") @RequestParam("corpId") String corpId, + @NotBlank(message = "staffId不能为空") @RequestParam("staffId") String staffId, @RequestParam(value = "contextSummary", required = false) String contextSummary) { IntentResult result = intentRecognitionService.recognize(message, contextSummary, customerId, corpId, staffId); @@ -51,8 +54,8 @@ public class IntentController { @GetMapping("/profile/{customerId}") public Result getProfile( - @PathVariable("customerId") String customerId, - @RequestParam("corpId") String corpId) { + @NotBlank(message = "customerId不能为空") @PathVariable("customerId") String customerId, + @NotBlank(message = "corpId不能为空") @RequestParam("corpId") String corpId) { CustomerProfile profile = customerProfileMapper.selectOne( new LambdaQueryWrapper() diff --git a/backend/intent-service/src/main/resources/application.yml b/backend/intent-service/src/main/resources/application.yml index 877583a..575f142 100644 --- a/backend/intent-service/src/main/resources/application.yml +++ b/backend/intent-service/src/main/resources/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8084 spring: @@ -6,7 +11,7 @@ spring: name: intent-service datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false + url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false username: ${MYSQL_USERNAME:root} password: ${MYSQL_PASSWORD:root} redis: diff --git a/backend/intent-service/src/test/java/com/artedu/intent/controller/IntentControllerTest.java b/backend/intent-service/src/test/java/com/artedu/intent/controller/IntentControllerTest.java new file mode 100644 index 0000000..532931b --- /dev/null +++ b/backend/intent-service/src/test/java/com/artedu/intent/controller/IntentControllerTest.java @@ -0,0 +1,123 @@ +package com.artedu.intent.controller; + +import com.artedu.intent.dto.IntentResult; +import com.artedu.intent.entity.CustomerProfile; +import com.artedu.intent.entity.IntentCategory; +import com.artedu.intent.mapper.CustomerProfileMapper; +import com.artedu.intent.mapper.IntentCategoryMapper; +import com.artedu.intent.service.IntentRecognitionService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class IntentControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private IntentRecognitionService intentRecognitionService; + + @MockBean + private IntentCategoryMapper intentCategoryMapper; + + @MockBean + private CustomerProfileMapper customerProfileMapper; + + @Test + void testRecognizeIntent() throws Exception { + IntentResult.IntentInfo primary = new IntentResult.IntentInfo(); + primary.setCode("INT-COURSE-01"); + primary.setName("课程咨询"); + primary.setConfidence(0.92); + + IntentResult result = new IntentResult(); + result.setPrimaryIntent(primary); + result.setConfidence(0.92); + + when(intentRecognitionService.recognize( + eq("我想学游戏美术"), isNull(), eq("cust001"), eq("corp001"), eq("staff001"))) + .thenReturn(result); + + mockMvc.perform(post("/api/v1/intent/recognize") + .param("message", "我想学游戏美术") + .param("customerId", "cust001") + .param("corpId", "corp001") + .param("staffId", "staff001")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.primaryIntent.code").value("INT-COURSE-01")) + .andExpect(jsonPath("$.data.primaryIntent.name").value("课程咨询")) + .andExpect(jsonPath("$.data.confidence").value(0.92)); + } + + @Test + void testListCategories() throws Exception { + IntentCategory cat1 = new IntentCategory(); + cat1.setCode("INT-COURSE-01"); + cat1.setName("课程咨询"); + cat1.setStatus(1); + + IntentCategory cat2 = new IntentCategory(); + cat2.setCode("INT-PRICE-01"); + cat2.setName("价格咨询"); + cat2.setStatus(1); + + when(intentCategoryMapper.selectList(any(LambdaQueryWrapper.class))) + .thenReturn(List.of(cat1, cat2)); + + mockMvc.perform(get("/api/v1/intent/categories")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.length()").value(2)) + .andExpect(jsonPath("$.data[0].code").value("INT-COURSE-01")); + } + + @Test + void testGetProfile() throws Exception { + CustomerProfile profile = new CustomerProfile(); + profile.setCustomerId("wx_001"); + profile.setCorpId("corp001"); + profile.setStudentType("在校大学生"); + profile.setSkillLevel("零基础"); + profile.setIntentLevel("高"); + profile.setIntentScore(75.0); + + when(customerProfileMapper.selectOne(any(LambdaQueryWrapper.class))) + .thenReturn(profile); + + mockMvc.perform(get("/api/v1/intent/profile/wx_001") + .param("corpId", "corp001")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.customerId").value("wx_001")) + .andExpect(jsonPath("$.data.studentType").value("在校大学生")) + .andExpect(jsonPath("$.data.intentScore").value(75.0)); + } + + @Test + void testGetProfileNotFound() throws Exception { + when(customerProfileMapper.selectOne(any(LambdaQueryWrapper.class))) + .thenReturn(null); + + mockMvc.perform(get("/api/v1/intent/profile/unknown") + .param("corpId", "corp001")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").isEmpty()); + } +} diff --git a/backend/intent-service/target/classes/application.yml b/backend/intent-service/target/classes/application.yml index 222fec7..575f142 100644 --- a/backend/intent-service/target/classes/application.yml +++ b/backend/intent-service/target/classes/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8084 spring: diff --git a/backend/intent-service/target/classes/com/artedu/intent/IntentServiceApplication.class b/backend/intent-service/target/classes/com/artedu/intent/IntentServiceApplication.class index 000c424..e708257 100644 Binary files a/backend/intent-service/target/classes/com/artedu/intent/IntentServiceApplication.class and b/backend/intent-service/target/classes/com/artedu/intent/IntentServiceApplication.class differ diff --git a/backend/intent-service/target/classes/com/artedu/intent/controller/IntentController.class b/backend/intent-service/target/classes/com/artedu/intent/controller/IntentController.class index e30dd1c..888424c 100644 Binary files a/backend/intent-service/target/classes/com/artedu/intent/controller/IntentController.class and b/backend/intent-service/target/classes/com/artedu/intent/controller/IntentController.class differ diff --git a/backend/intent-service/target/classes/com/artedu/intent/dto/IntentResult$IntentInfo.class b/backend/intent-service/target/classes/com/artedu/intent/dto/IntentResult$IntentInfo.class index 6d90c67..c3f7d59 100644 Binary files a/backend/intent-service/target/classes/com/artedu/intent/dto/IntentResult$IntentInfo.class and b/backend/intent-service/target/classes/com/artedu/intent/dto/IntentResult$IntentInfo.class differ diff --git a/backend/intent-service/target/classes/com/artedu/intent/dto/IntentResult.class b/backend/intent-service/target/classes/com/artedu/intent/dto/IntentResult.class index 4520583..736fabb 100644 Binary files a/backend/intent-service/target/classes/com/artedu/intent/dto/IntentResult.class and b/backend/intent-service/target/classes/com/artedu/intent/dto/IntentResult.class differ diff --git a/backend/intent-service/target/classes/com/artedu/intent/entity/CustomerProfile.class b/backend/intent-service/target/classes/com/artedu/intent/entity/CustomerProfile.class index f097c03..8bd743c 100644 Binary files a/backend/intent-service/target/classes/com/artedu/intent/entity/CustomerProfile.class and b/backend/intent-service/target/classes/com/artedu/intent/entity/CustomerProfile.class differ diff --git a/backend/intent-service/target/classes/com/artedu/intent/entity/IntentCategory.class b/backend/intent-service/target/classes/com/artedu/intent/entity/IntentCategory.class index 3302a11..87534f8 100644 Binary files a/backend/intent-service/target/classes/com/artedu/intent/entity/IntentCategory.class and b/backend/intent-service/target/classes/com/artedu/intent/entity/IntentCategory.class differ diff --git a/backend/intent-service/target/classes/com/artedu/intent/entity/IntentRecognition.class b/backend/intent-service/target/classes/com/artedu/intent/entity/IntentRecognition.class index c873a63..655a1f6 100644 Binary files a/backend/intent-service/target/classes/com/artedu/intent/entity/IntentRecognition.class and b/backend/intent-service/target/classes/com/artedu/intent/entity/IntentRecognition.class differ diff --git a/backend/intent-service/target/classes/com/artedu/intent/mapper/CustomerProfileMapper.class b/backend/intent-service/target/classes/com/artedu/intent/mapper/CustomerProfileMapper.class index 8d9e6d4..ab21922 100644 Binary files a/backend/intent-service/target/classes/com/artedu/intent/mapper/CustomerProfileMapper.class and b/backend/intent-service/target/classes/com/artedu/intent/mapper/CustomerProfileMapper.class differ diff --git a/backend/intent-service/target/classes/com/artedu/intent/mapper/IntentCategoryMapper.class b/backend/intent-service/target/classes/com/artedu/intent/mapper/IntentCategoryMapper.class index 76a57bd..f7b7643 100644 Binary files a/backend/intent-service/target/classes/com/artedu/intent/mapper/IntentCategoryMapper.class and b/backend/intent-service/target/classes/com/artedu/intent/mapper/IntentCategoryMapper.class differ diff --git a/backend/intent-service/target/classes/com/artedu/intent/mapper/IntentRecognitionMapper.class b/backend/intent-service/target/classes/com/artedu/intent/mapper/IntentRecognitionMapper.class index ef5a896..8eabddd 100644 Binary files a/backend/intent-service/target/classes/com/artedu/intent/mapper/IntentRecognitionMapper.class and b/backend/intent-service/target/classes/com/artedu/intent/mapper/IntentRecognitionMapper.class differ diff --git a/backend/intent-service/target/classes/com/artedu/intent/service/IntentRecognitionService.class b/backend/intent-service/target/classes/com/artedu/intent/service/IntentRecognitionService.class index 45df181..fa85717 100644 Binary files a/backend/intent-service/target/classes/com/artedu/intent/service/IntentRecognitionService.class and b/backend/intent-service/target/classes/com/artedu/intent/service/IntentRecognitionService.class differ diff --git a/backend/intent-service/target/classes/com/artedu/intent/service/LLMIntentClient.class b/backend/intent-service/target/classes/com/artedu/intent/service/LLMIntentClient.class index 5b19523..4eee286 100644 Binary files a/backend/intent-service/target/classes/com/artedu/intent/service/LLMIntentClient.class and b/backend/intent-service/target/classes/com/artedu/intent/service/LLMIntentClient.class differ diff --git a/backend/intent-service/target/intent-service-1.0.0-SNAPSHOT.jar b/backend/intent-service/target/intent-service-1.0.0-SNAPSHOT.jar index eac2b07..6ecd845 100644 Binary files a/backend/intent-service/target/intent-service-1.0.0-SNAPSHOT.jar and b/backend/intent-service/target/intent-service-1.0.0-SNAPSHOT.jar differ diff --git a/backend/intent-service/target/intent-service-1.0.0-SNAPSHOT.jar.original b/backend/intent-service/target/intent-service-1.0.0-SNAPSHOT.jar.original new file mode 100644 index 0000000..c8840d3 Binary files /dev/null and b/backend/intent-service/target/intent-service-1.0.0-SNAPSHOT.jar.original differ diff --git a/backend/intent-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/backend/intent-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..db5d35a --- /dev/null +++ b/backend/intent-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst @@ -0,0 +1 @@ +com\artedu\intent\controller\IntentControllerTest.class diff --git a/backend/intent-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/backend/intent-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..5e6647d --- /dev/null +++ b/backend/intent-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1 @@ +D:\www\agent_9art\backend\intent-service\src\test\java\com\artedu\intent\controller\IntentControllerTest.java diff --git a/backend/intent-service/target/test-classes/com/artedu/intent/controller/IntentControllerTest.class b/backend/intent-service/target/test-classes/com/artedu/intent/controller/IntentControllerTest.class new file mode 100644 index 0000000..d11ce72 Binary files /dev/null and b/backend/intent-service/target/test-classes/com/artedu/intent/controller/IntentControllerTest.class differ diff --git a/backend/kb-admin-service/Dockerfile b/backend/kb-admin-service/Dockerfile index 858c6fb..36185a2 100644 --- a/backend/kb-admin-service/Dockerfile +++ b/backend/kb-admin-service/Dockerfile @@ -1,5 +1,5 @@ -FROM openjdk:17-jdk-slim +FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY target/*.jar app.jar -EXPOSE 8080 +EXPOSE 8087 ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/backend/kb-admin-service/pom.xml b/backend/kb-admin-service/pom.xml index ac96b88..d6d412e 100644 --- a/backend/kb-admin-service/pom.xml +++ b/backend/kb-admin-service/pom.xml @@ -21,6 +21,34 @@ org.springframework.boot spring-boot-starter-web + + io.jsonwebtoken + jjwt-api + + + io.jsonwebtoken + jjwt-impl + runtime + + + io.jsonwebtoken + jjwt-jackson + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + com.h2database + h2 + test + + + org.springframework.security + spring-security-crypto + diff --git a/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/config/SecurityConfig.java b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/config/SecurityConfig.java new file mode 100644 index 0000000..510bc79 --- /dev/null +++ b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/config/SecurityConfig.java @@ -0,0 +1,14 @@ +package com.artedu.kbadmin.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@Configuration +public class SecurityConfig { + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/controller/AdminController.java b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/controller/AdminController.java new file mode 100644 index 0000000..14f6e1f --- /dev/null +++ b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/controller/AdminController.java @@ -0,0 +1,97 @@ +package com.artedu.kbadmin.controller; + +import com.artedu.common.result.PageResult; +import com.artedu.common.result.Result; +import com.artedu.kbadmin.entity.Admin; +import com.artedu.kbadmin.mapper.AdminMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import javax.validation.Valid; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@Validated +@RestController +@RequestMapping("/api/v1/admin/admins") +public class AdminController { + + @Autowired + private AdminMapper adminMapper; + + @Autowired + private BCryptPasswordEncoder passwordEncoder; + + @GetMapping + public Result> list( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "20") Integer size, + @RequestParam(required = false) String keyword) { + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (keyword != null && !keyword.isEmpty()) { + wrapper.like(Admin::getUsername, keyword) + .or().like(Admin::getName, keyword); + } + wrapper.orderByDesc(Admin::getCreatedAt); + + Page result = adminMapper.selectPage(new Page<>(page, size), wrapper); + List records = result.getRecords(); + for (Admin a : records) { + a.setPassword(null); + } + return Result.success(PageResult.of(page, size, result.getTotal(), records)); + } + + @GetMapping("/{id}") + public Result getById(@PathVariable Long id) { + Admin admin = adminMapper.selectById(id); + if (admin != null) { + admin.setPassword(null); + } + return Result.success(admin); + } + + @PostMapping + public Result create(@Valid @RequestBody Admin admin) { + admin.setCreatedAt(LocalDateTime.now()); + admin.setUpdatedAt(LocalDateTime.now()); + admin.setStatus("ACTIVE"); + // BCrypt 加密密码 + if (admin.getPassword() != null && !admin.getPassword().isEmpty()) { + admin.setPassword(passwordEncoder.encode(admin.getPassword())); + } + adminMapper.insert(admin); + log.info("创建管理员: username={}", admin.getUsername()); + return Result.success("创建成功"); + } + + @PutMapping("/{id}") + public Result update(@PathVariable Long id, @Valid @RequestBody Admin admin) { + admin.setId(id); + admin.setUpdatedAt(LocalDateTime.now()); + // 密码为空时不更新密码;有密码时 BCrypt 加密 + if (admin.getPassword() == null || admin.getPassword().isEmpty()) { + Admin existing = adminMapper.selectById(id); + if (existing != null) { + admin.setPassword(existing.getPassword()); + } + } else { + admin.setPassword(passwordEncoder.encode(admin.getPassword())); + } + adminMapper.updateById(admin); + return Result.success("更新成功"); + } + + @DeleteMapping("/{id}") + public Result delete(@PathVariable Long id) { + adminMapper.deleteById(id); + return Result.success("删除成功"); + } +} diff --git a/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/controller/AuthController.java b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/controller/AuthController.java new file mode 100644 index 0000000..c88e455 --- /dev/null +++ b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/controller/AuthController.java @@ -0,0 +1,132 @@ +package com.artedu.kbadmin.controller; + +import com.artedu.common.result.Result; +import com.artedu.kbadmin.entity.Admin; +import com.artedu.kbadmin.mapper.AdminMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@Validated +@RestController +@RequestMapping("/api/v1/admin/auth") +public class AuthController { + + @Autowired + private AdminMapper adminMapper; + + @Autowired + private BCryptPasswordEncoder passwordEncoder; + + @PostConstruct + public void initAdminPassword() { + try { + Admin admin = adminMapper.selectOne( + new LambdaQueryWrapper().eq(Admin::getUsername, "admin") + ); + if (admin != null) { + String newHash = passwordEncoder.encode("admin123"); + admin.setPassword(newHash); + adminMapper.updateById(admin); + log.info("Admin 密码已自动同步为 admin123"); + } + } catch (Exception e) { + log.error("同步 admin 密码失败: {}", e.getMessage()); + } + } + + @Value("${jwt.secret:your-256-bit-secret-key-here-at-least-32-characters}") + private String jwtSecret; + + @PostMapping("/login") + public Result> login(@RequestBody Map params, HttpServletRequest request) { + String username = params.get("username"); + String password = params.get("password"); + + Admin admin = adminMapper.selectOne( + new LambdaQueryWrapper() + .eq(Admin::getUsername, username) + .eq(Admin::getStatus, "ACTIVE") + ); + + if (admin == null || !passwordEncoder.matches(password, admin.getPassword())) { + return Result.fail("用户名或密码错误"); + } + + // 更新最后登录时间 + admin.setLastLoginTime(java.time.LocalDateTime.now()); + admin.setLastLoginIp(getClientIp(request)); + adminMapper.updateById(admin); + + // 生成 JWT + String token = Jwts.builder() + .setSubject(admin.getUsername()) + .claim("adminId", admin.getId()) + .claim("role", admin.getRole()) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 86400000)) + .signWith(SignatureAlgorithm.HS256, jwtSecret.getBytes()) + .compact(); + + Map result = new HashMap<>(); + result.put("token", token); + result.put("tokenType", "Bearer"); + result.put("expiresIn", 86400); + result.put("adminId", admin.getId()); + result.put("username", admin.getUsername()); + result.put("name", admin.getName()); + result.put("role", admin.getRole()); + + log.info("管理员登录成功: username={}", username); + return Result.success(result); + } + + @GetMapping("/info") + public Result> getInfo(@RequestHeader("Authorization") String authHeader) { + String token = authHeader.replace("Bearer ", ""); + io.jsonwebtoken.Claims claims = Jwts.parser() + .setSigningKey(jwtSecret.getBytes()) + .parseClaimsJws(token) + .getBody(); + + String username = claims.getSubject(); + Admin admin = adminMapper.selectOne( + new LambdaQueryWrapper().eq(Admin::getUsername, username) + ); + + if (admin == null) { + return Result.fail("管理员不存在"); + } + + Map result = new HashMap<>(); + result.put("id", admin.getId()); + result.put("username", admin.getUsername()); + result.put("name", admin.getName()); + result.put("role", admin.getRole()); + result.put("email", admin.getEmail()); + result.put("phone", admin.getPhone()); + + return Result.success(result); + } + + private String getClientIp(HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + if (ip == null || ip.isEmpty()) { + ip = request.getRemoteAddr(); + } + return ip.split(",")[0].trim(); + } +} diff --git a/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/controller/KnowledgeController.java b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/controller/KnowledgeController.java new file mode 100644 index 0000000..2dedb23 --- /dev/null +++ b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/controller/KnowledgeController.java @@ -0,0 +1,128 @@ +package com.artedu.kbadmin.controller; + +import com.artedu.common.result.PageResult; +import com.artedu.common.result.Result; +import com.artedu.kbadmin.entity.KnowledgeDocument; +import com.artedu.kbadmin.mapper.KnowledgeDocumentMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import javax.validation.Valid; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.UUID; + +@Slf4j +@Validated +@RestController +@RequestMapping("/api/v1/admin/knowledge") +public class KnowledgeController { + + @Autowired + private KnowledgeDocumentMapper knowledgeDocumentMapper; + + @GetMapping + public Result> list( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "20") Integer size, + @RequestParam(required = false) String keyword) { + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (keyword != null && !keyword.isEmpty()) { + wrapper.like(KnowledgeDocument::getTitle, keyword) + .or().like(KnowledgeDocument::getContentText, keyword); + } + wrapper.orderByDesc(KnowledgeDocument::getCreatedAt); + + Page result = knowledgeDocumentMapper.selectPage(new Page<>(page, size), wrapper); + return Result.success(PageResult.of(page, size, result.getTotal(), result.getRecords())); + } + + @GetMapping("/{id}") + public Result getById(@PathVariable Long id) { + return Result.success(knowledgeDocumentMapper.selectById(id)); + } + + @PostMapping + public Result create(@Valid @RequestBody KnowledgeDocument doc) { + doc.setDocId(UUID.randomUUID().toString().replace("-", "")); + doc.setCorpId("corp001"); + doc.setStatus("ACTIVE"); + doc.setHasVector(0); + doc.setCreatedAt(LocalDateTime.now()); + doc.setUpdatedAt(LocalDateTime.now()); + knowledgeDocumentMapper.insert(doc); + log.info("创建知识库文档: title={}", doc.getTitle()); + return Result.success("创建成功"); + } + + @PutMapping("/{id}") + public Result update(@PathVariable Long id, @Valid @RequestBody KnowledgeDocument doc) { + doc.setId(id); + doc.setUpdatedAt(LocalDateTime.now()); + knowledgeDocumentMapper.updateById(doc); + return Result.success("更新成功"); + } + + @DeleteMapping("/{id}") + public Result delete(@PathVariable Long id) { + knowledgeDocumentMapper.deleteById(id); + return Result.success("删除成功"); + } + + @PostMapping("/upload") + public Result upload(@RequestParam("file") MultipartFile file, + @RequestParam(value = "category", required = false) String category) { + try { + String originalName = file.getOriginalFilename(); + String docType = getDocType(originalName); + StringBuilder content = new StringBuilder(); + + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + content.append(line).append("\n"); + } + } + + KnowledgeDocument doc = new KnowledgeDocument(); + doc.setDocId(UUID.randomUUID().toString().replace("-", "")); + doc.setCorpId("corp001"); + doc.setTitle(originalName); + doc.setContent(content.toString()); + doc.setContentText(content.toString()); + doc.setDocType(docType); + doc.setCategory(category != null ? category : "默认分类"); + doc.setStatus("ACTIVE"); + doc.setHasVector(0); + doc.setCreatedAt(LocalDateTime.now()); + doc.setUpdatedAt(LocalDateTime.now()); + knowledgeDocumentMapper.insert(doc); + + log.info("上传知识库文档: title={}, type={}, size={} chars", originalName, docType, content.length()); + return Result.success(doc); + } catch (Exception e) { + log.error("上传知识库文档失败", e); + return Result.fail("上传失败: " + e.getMessage()); + } + } + + private String getDocType(String filename) { + if (filename == null) return "UNKNOWN"; + String lower = filename.toLowerCase(); + if (lower.endsWith(".txt")) return "TXT"; + if (lower.endsWith(".md")) return "MARKDOWN"; + if (lower.endsWith(".pdf")) return "PDF"; + if (lower.endsWith(".docx")) return "DOCX"; + if (lower.endsWith(".doc")) return "DOC"; + return "UNKNOWN"; + } +} diff --git a/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/controller/UtteranceController.java b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/controller/UtteranceController.java index bbfe7a5..6897284 100644 --- a/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/controller/UtteranceController.java +++ b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/controller/UtteranceController.java @@ -6,13 +6,16 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import javax.validation.Valid; import java.time.LocalDateTime; import java.util.List; import java.util.Map; @Slf4j +@Validated @RestController @RequestMapping("/api/v1/admin/utterances") public class UtteranceController { @@ -47,7 +50,7 @@ public class UtteranceController { } @PostMapping - public Result create(@RequestBody com.artedu.kbadmin.entity.Utterance utterance) { + public Result create(@Valid @RequestBody com.artedu.kbadmin.entity.Utterance utterance) { utterance.setCreatedAt(LocalDateTime.now()); utterance.setUpdatedAt(LocalDateTime.now()); utteranceMapper.insert(utterance); @@ -55,7 +58,7 @@ public class UtteranceController { } @PutMapping("/{id}") - public Result update(@PathVariable Long id, @RequestBody com.artedu.kbadmin.entity.Utterance utterance) { + public Result update(@PathVariable Long id, @Valid @RequestBody com.artedu.kbadmin.entity.Utterance utterance) { utterance.setId(id); utterance.setUpdatedAt(LocalDateTime.now()); utteranceMapper.updateById(utterance); diff --git a/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/entity/Admin.java b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/entity/Admin.java new file mode 100644 index 0000000..c2e722c --- /dev/null +++ b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/entity/Admin.java @@ -0,0 +1,38 @@ +package com.artedu.kbadmin.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.time.LocalDateTime; + +@Data +@TableName("admins") +public class Admin { + + @TableId(type = IdType.AUTO) + private Long id; + + @NotBlank(message = "用户名不能为空") + @Size(min = 3, max = 50, message = "用户名长度必须在3-50之间") + private String username; + + @NotBlank(message = "密码不能为空") + @Size(min = 6, max = 100, message = "密码长度必须在6-100之间") + private String password; + + @NotBlank(message = "姓名不能为空") + private String name; + + private String email; + private String phone; + private String role; + private String status; + private LocalDateTime lastLoginTime; + private String lastLoginIp; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/entity/KnowledgeDocument.java b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/entity/KnowledgeDocument.java new file mode 100644 index 0000000..297c9ce --- /dev/null +++ b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/entity/KnowledgeDocument.java @@ -0,0 +1,32 @@ +package com.artedu.kbadmin.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("knowledge_documents") +public class KnowledgeDocument { + + @TableId(type = IdType.AUTO) + private Long id; + + private String docId; + private String corpId; + private String title; + private String content; + private String contentText; + private String docType; + private String category; + private String tags; + private String status; + private Integer hasVector; + private LocalDateTime vectorUpdatedAt; + private String createdBy; + private String updatedBy; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/entity/Utterance.java b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/entity/Utterance.java index be69f15..e125e64 100644 --- a/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/entity/Utterance.java +++ b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/entity/Utterance.java @@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import java.time.LocalDateTime; @Data @@ -15,9 +17,16 @@ public class Utterance { private Long id; private String utteranceId; + + @NotBlank(message = "企业ID不能为空") private String corpId; + + @NotBlank(message = "话术标题不能为空") private String title; + + @NotBlank(message = "话术内容不能为空") private String content; + private String contentText; private Integer version; private String status; diff --git a/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/mapper/AdminMapper.java b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/mapper/AdminMapper.java new file mode 100644 index 0000000..9e801ee --- /dev/null +++ b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/mapper/AdminMapper.java @@ -0,0 +1,9 @@ +package com.artedu.kbadmin.mapper; + +import com.artedu.kbadmin.entity.Admin; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface AdminMapper extends BaseMapper { +} diff --git a/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/mapper/KnowledgeDocumentMapper.java b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/mapper/KnowledgeDocumentMapper.java new file mode 100644 index 0000000..b40f9f9 --- /dev/null +++ b/backend/kb-admin-service/src/main/java/com/artedu/kbadmin/mapper/KnowledgeDocumentMapper.java @@ -0,0 +1,9 @@ +package com.artedu.kbadmin.mapper; + +import com.artedu.kbadmin.entity.KnowledgeDocument; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface KnowledgeDocumentMapper extends BaseMapper { +} diff --git a/backend/kb-admin-service/src/main/resources/application.yml b/backend/kb-admin-service/src/main/resources/application.yml index f1049ab..cc918ff 100644 --- a/backend/kb-admin-service/src/main/resources/application.yml +++ b/backend/kb-admin-service/src/main/resources/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8087 spring: @@ -6,7 +11,7 @@ spring: name: kb-admin-service datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false + url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false username: ${MYSQL_USERNAME:root} password: ${MYSQL_PASSWORD:root} redis: @@ -24,6 +29,14 @@ spring: port: ${RABBITMQ_PORT:5672} username: ${RABBITMQ_USERNAME:admin} password: ${RABBITMQ_PASSWORD:admin} + servlet: + multipart: + enabled: true + max-file-size: 50MB + max-request-size: 100MB + +jwt: + secret: ${JWT_SECRET:your-256-bit-secret-key-here-at-least-32-characters} mybatis-plus: configuration: diff --git a/backend/kb-admin-service/src/test/java/com/artedu/kbadmin/controller/AuthControllerTest.java b/backend/kb-admin-service/src/test/java/com/artedu/kbadmin/controller/AuthControllerTest.java new file mode 100644 index 0000000..0b3d863 --- /dev/null +++ b/backend/kb-admin-service/src/test/java/com/artedu/kbadmin/controller/AuthControllerTest.java @@ -0,0 +1,92 @@ +package com.artedu.kbadmin.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.hamcrest.Matchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +public class AuthControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Test + void testLoginSuccess() throws Exception { + mockMvc.perform(post("/api/v1/admin/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"username\":\"admin\",\"password\":\"admin123\"}")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data.token").exists()) + .andExpect(jsonPath("$.data.role").value("SUPER_ADMIN")); + } + + @Test + void testLoginFailWithWrongPassword() throws Exception { + mockMvc.perform(post("/api/v1/admin/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"username\":\"admin\",\"password\":\"wrongpass\"}")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(-1)) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.message").value("用户名或密码错误")); + } + + @Test + void testLoginFailWithNonExistentUser() throws Exception { + mockMvc.perform(post("/api/v1/admin/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"username\":\"notexist\",\"password\":\"admin123\"}")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(-1)) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.message").value("用户名或密码错误")); + } + + @Test + void testGetInfoWithValidToken() throws Exception { + // 先登录获取token + MvcResult loginResult = mockMvc.perform(post("/api/v1/admin/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"username\":\"admin\",\"password\":\"admin123\"}")) + .andReturn(); + + String response = loginResult.getResponse().getContentAsString(); + String token = objectMapper.readTree(response).path("data").path("token").asText(); + + // 使用token获取用户信息 + mockMvc.perform(get("/api/v1/admin/auth/info") + .header("Authorization", "Bearer " + token)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.username").value("admin")) + .andExpect(jsonPath("$.data.role").value("SUPER_ADMIN")); + } + + @Test + void testGetInfoWithInvalidToken() throws Exception { + mockMvc.perform(get("/api/v1/admin/auth/info") + .header("Authorization", "Bearer invalid_token")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(500)) + .andExpect(jsonPath("$.success").value(false)); + } +} diff --git a/backend/kb-admin-service/src/test/java/com/artedu/kbadmin/controller/UtteranceControllerTest.java b/backend/kb-admin-service/src/test/java/com/artedu/kbadmin/controller/UtteranceControllerTest.java new file mode 100644 index 0000000..d3befb1 --- /dev/null +++ b/backend/kb-admin-service/src/test/java/com/artedu/kbadmin/controller/UtteranceControllerTest.java @@ -0,0 +1,93 @@ +package com.artedu.kbadmin.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.hamcrest.Matchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +public class UtteranceControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + private String adminToken; + + @BeforeEach + void setUp() throws Exception { + MvcResult result = mockMvc.perform(post("/api/v1/admin/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"username\":\"admin\",\"password\":\"admin123\"}")) + .andReturn(); + String response = result.getResponse().getContentAsString(); + adminToken = objectMapper.readTree(response).path("data").path("token").asText(); + } + + @Test + void testListUtterances() throws Exception { + mockMvc.perform(get("/api/v1/admin/utterances?page=1&size=10") + .header("Authorization", "Bearer " + adminToken)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.list").isArray()); + } + + @Test + void testGetUtteranceById() throws Exception { + mockMvc.perform(get("/api/v1/admin/utterances/1") + .header("Authorization", "Bearer " + adminToken)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.utteranceId").exists()); + } + + @Test + void testCreateAndDeleteUtterance() throws Exception { + String json = "{\"utteranceId\":\"UTT-TEST-001\",\"corpId\":\"corp001\",\"title\":\"测试话术\",\"content\":\"测试内容\",\"contentText\":\"测试内容\",\"status\":\"ACTIVE\",\"priority\":5,\"stageTags\":\"[\\\"STAGE-01\\\"]\",\"intentTags\":\"[\\\"INT-GREET-01\\\"]\"}"; + + // 创建 + MvcResult createResult = mockMvc.perform(post("/api/v1/admin/utterances") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", "Bearer " + adminToken) + .content(json)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andReturn(); + + // 查询列表确认创建成功 + mockMvc.perform(get("/api/v1/admin/utterances?page=1&size=100") + .header("Authorization", "Bearer " + adminToken)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.list[*].utteranceId").value(hasItem("UTT-TEST-001"))); + } + + @Test + void testCreateUtteranceMissingRequiredFields_shouldReturn400() throws Exception { + // 缺少 corpId、title、contentText 等必填字段,触发 MethodArgumentNotValidException + String json = "{\"utteranceId\":\"UTT-TEST-BAD\",\"status\":\"ACTIVE\"}"; + + mockMvc.perform(post("/api/v1/admin/utterances") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", "Bearer " + adminToken) + .content(json)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(400)) + .andExpect(jsonPath("$.message").exists()); + } +} diff --git a/backend/kb-admin-service/src/test/resources/application-test.yml b/backend/kb-admin-service/src/test/resources/application-test.yml new file mode 100644 index 0000000..7da147c --- /dev/null +++ b/backend/kb-admin-service/src/test/resources/application-test.yml @@ -0,0 +1,27 @@ +spring: + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + username: sa + password: + sql: + init: + mode: always + schema-locations: classpath:schema.sql + data-locations: classpath:data.sql + redis: + host: localhost + port: 6379 + +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + logic-delete-field: deleted + logic-delete-value: 1 + logic-not-delete-value: 0 + +jwt: + secret: test-jwt-secret-key-2024-for-unit-testing-only + expiration: 86400 diff --git a/backend/kb-admin-service/src/test/resources/data.sql b/backend/kb-admin-service/src/test/resources/data.sql new file mode 100644 index 0000000..823b536 --- /dev/null +++ b/backend/kb-admin-service/src/test/resources/data.sql @@ -0,0 +1,6 @@ +INSERT INTO admins (id, username, password, name, email, role, status, created_at, updated_at) VALUES +(1, 'admin', '$2b$10$.jFUiPdJDb/4ksDJWbPdauG2VZjBsG3qJ68DnCQ8YvXyY0UtnpPvW', '系统管理员', 'admin@9artedu.com', 'SUPER_ADMIN', 'ACTIVE', NOW(), NOW()); + +INSERT INTO utterances (utterance_id, corp_id, title, content, content_text, status, priority, stage_tags, intent_tags, profile_tags, success_rate, created_by, source) VALUES +('UTT-001-001', 'corp001', '标准开场白', '您好!欢迎咨询第九联盟CG艺术教育。', '您好!欢迎咨询第九联盟CG艺术教育。', 'ACTIVE', 9, '["STAGE-01"]', '["INT-GREET-01"]', '["通用"]', 0.8205, 'admin', 'MANUAL'), +('UTT-003-001', 'corp001', '零基础推荐-游戏美术', '根据您零基础的情况,我强烈推荐咱们的「游戏美术全能班」。', '根据您零基础的情况,我强烈推荐咱们的「游戏美术全能班」。', 'ACTIVE', 10, '["STAGE-03"]', '["INT-COURSE-01"]', '["零基础"]', 0.8589, 'admin', 'MANUAL'); diff --git a/backend/kb-admin-service/src/test/resources/schema.sql b/backend/kb-admin-service/src/test/resources/schema.sql new file mode 100644 index 0000000..638fb47 --- /dev/null +++ b/backend/kb-admin-service/src/test/resources/schema.sql @@ -0,0 +1,69 @@ +CREATE TABLE IF NOT EXISTS admins ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(64) NOT NULL UNIQUE, + password VARCHAR(256) NOT NULL, + name VARCHAR(64), + email VARCHAR(128), + phone VARCHAR(32), + role VARCHAR(32) DEFAULT 'ADMIN', + status VARCHAR(32) DEFAULT 'ACTIVE', + last_login_time TIMESTAMP, + last_login_ip VARCHAR(64), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS utterances ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + utterance_id VARCHAR(64) NOT NULL UNIQUE, + corp_id VARCHAR(64) NOT NULL, + title VARCHAR(128) NOT NULL, + content TEXT NOT NULL, + content_text TEXT, + version INT DEFAULT 1, + status VARCHAR(32) DEFAULT 'DRAFT', + priority INT DEFAULT 5, + stage_tags TEXT, + intent_tags TEXT, + profile_tags TEXT, + course_type_tags TEXT, + emotion_tags TEXT, + topic_tags TEXT, + used_count INT DEFAULT 0, + selected_count INT DEFAULT 0, + positive_feedback INT DEFAULT 0, + negative_feedback INT DEFAULT 0, + conversion_count INT DEFAULT 0, + success_rate DECIMAL(5,4) DEFAULT 0.0000, + avg_response_time_ms INT, + has_vector TINYINT DEFAULT 0, + vector_updated_at TIMESTAMP, + trigger_keywords TEXT, + variables TEXT, + created_by VARCHAR(64), + reviewed_by VARCHAR(64), + source VARCHAR(32) DEFAULT 'MANUAL', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted INT DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS knowledge_documents ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + doc_id VARCHAR(64) NOT NULL UNIQUE, + corp_id VARCHAR(64) NOT NULL, + title VARCHAR(256) NOT NULL, + content TEXT, + content_text TEXT, + doc_type VARCHAR(32), + category VARCHAR(64), + tags TEXT, + status VARCHAR(32) DEFAULT 'ACTIVE', + has_vector TINYINT DEFAULT 0, + vector_updated_at TIMESTAMP, + created_by VARCHAR(64), + updated_by VARCHAR(64), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted INT DEFAULT 0 +); diff --git a/backend/kb-admin-service/target/classes/application.yml b/backend/kb-admin-service/target/classes/application.yml index 2b92406..cc918ff 100644 --- a/backend/kb-admin-service/target/classes/application.yml +++ b/backend/kb-admin-service/target/classes/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8087 spring: @@ -24,6 +29,14 @@ spring: port: ${RABBITMQ_PORT:5672} username: ${RABBITMQ_USERNAME:admin} password: ${RABBITMQ_PASSWORD:admin} + servlet: + multipart: + enabled: true + max-file-size: 50MB + max-request-size: 100MB + +jwt: + secret: ${JWT_SECRET:your-256-bit-secret-key-here-at-least-32-characters} mybatis-plus: configuration: diff --git a/backend/kb-admin-service/target/classes/com/artedu/kbadmin/config/SecurityConfig.class b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/config/SecurityConfig.class new file mode 100644 index 0000000..d9b0823 Binary files /dev/null and b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/config/SecurityConfig.class differ diff --git a/backend/kb-admin-service/target/classes/com/artedu/kbadmin/controller/AdminController.class b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/controller/AdminController.class new file mode 100644 index 0000000..1cdc1dd Binary files /dev/null and b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/controller/AdminController.class differ diff --git a/backend/kb-admin-service/target/classes/com/artedu/kbadmin/controller/AuthController.class b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/controller/AuthController.class new file mode 100644 index 0000000..ecca56d Binary files /dev/null and b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/controller/AuthController.class differ diff --git a/backend/kb-admin-service/target/classes/com/artedu/kbadmin/controller/KnowledgeController.class b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/controller/KnowledgeController.class new file mode 100644 index 0000000..9d621bb Binary files /dev/null and b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/controller/KnowledgeController.class differ diff --git a/backend/kb-admin-service/target/classes/com/artedu/kbadmin/controller/UtteranceController.class b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/controller/UtteranceController.class index bdef47d..33b93be 100644 Binary files a/backend/kb-admin-service/target/classes/com/artedu/kbadmin/controller/UtteranceController.class and b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/controller/UtteranceController.class differ diff --git a/backend/kb-admin-service/target/classes/com/artedu/kbadmin/entity/Admin.class b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/entity/Admin.class new file mode 100644 index 0000000..f74c20f Binary files /dev/null and b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/entity/Admin.class differ diff --git a/backend/kb-admin-service/target/classes/com/artedu/kbadmin/entity/KnowledgeDocument.class b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/entity/KnowledgeDocument.class new file mode 100644 index 0000000..43d2160 Binary files /dev/null and b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/entity/KnowledgeDocument.class differ diff --git a/backend/kb-admin-service/target/classes/com/artedu/kbadmin/entity/Utterance.class b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/entity/Utterance.class index 1f55fe7..80f92dd 100644 Binary files a/backend/kb-admin-service/target/classes/com/artedu/kbadmin/entity/Utterance.class and b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/entity/Utterance.class differ diff --git a/backend/kb-admin-service/target/classes/com/artedu/kbadmin/mapper/AdminMapper.class b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/mapper/AdminMapper.class new file mode 100644 index 0000000..ba08e67 Binary files /dev/null and b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/mapper/AdminMapper.class differ diff --git a/backend/kb-admin-service/target/classes/com/artedu/kbadmin/mapper/KnowledgeDocumentMapper.class b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/mapper/KnowledgeDocumentMapper.class new file mode 100644 index 0000000..c601916 Binary files /dev/null and b/backend/kb-admin-service/target/classes/com/artedu/kbadmin/mapper/KnowledgeDocumentMapper.class differ diff --git a/backend/kb-admin-service/target/kb-admin-service-1.0.0-SNAPSHOT.jar b/backend/kb-admin-service/target/kb-admin-service-1.0.0-SNAPSHOT.jar index 79236a4..dd60cc4 100644 Binary files a/backend/kb-admin-service/target/kb-admin-service-1.0.0-SNAPSHOT.jar and b/backend/kb-admin-service/target/kb-admin-service-1.0.0-SNAPSHOT.jar differ diff --git a/backend/kb-admin-service/target/kb-admin-service-1.0.0-SNAPSHOT.jar.original b/backend/kb-admin-service/target/kb-admin-service-1.0.0-SNAPSHOT.jar.original new file mode 100644 index 0000000..12ed64c Binary files /dev/null and b/backend/kb-admin-service/target/kb-admin-service-1.0.0-SNAPSHOT.jar.original differ diff --git a/backend/kb-admin-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/backend/kb-admin-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index a7cac0f..f8341f4 100644 --- a/backend/kb-admin-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/backend/kb-admin-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,4 +1,12 @@ com\artedu\kbadmin\mapper\UtteranceMapper.class +com\artedu\kbadmin\config\SecurityConfig.class com\artedu\kbadmin\controller\UtteranceController.class -com\artedu\kbadmin\entity\Utterance.class com\artedu\kbadmin\KbAdminServiceApplication.class +com\artedu\kbadmin\controller\KnowledgeController.class +com\artedu\kbadmin\mapper\KnowledgeDocumentMapper.class +com\artedu\kbadmin\entity\Utterance.class +com\artedu\kbadmin\mapper\AdminMapper.class +com\artedu\kbadmin\entity\Admin.class +com\artedu\kbadmin\controller\AdminController.class +com\artedu\kbadmin\controller\AuthController.class +com\artedu\kbadmin\entity\KnowledgeDocument.class diff --git a/backend/kb-admin-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/backend/kb-admin-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index ff57cd1..1f7809c 100644 --- a/backend/kb-admin-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/backend/kb-admin-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -1,4 +1,12 @@ +D:\www\agent_9art\backend\kb-admin-service\src\main\java\com\artedu\kbadmin\config\SecurityConfig.java +D:\www\agent_9art\backend\kb-admin-service\src\main\java\com\artedu\kbadmin\controller\AdminController.java +D:\www\agent_9art\backend\kb-admin-service\src\main\java\com\artedu\kbadmin\controller\AuthController.java +D:\www\agent_9art\backend\kb-admin-service\src\main\java\com\artedu\kbadmin\controller\KnowledgeController.java D:\www\agent_9art\backend\kb-admin-service\src\main\java\com\artedu\kbadmin\controller\UtteranceController.java +D:\www\agent_9art\backend\kb-admin-service\src\main\java\com\artedu\kbadmin\entity\Admin.java +D:\www\agent_9art\backend\kb-admin-service\src\main\java\com\artedu\kbadmin\entity\KnowledgeDocument.java D:\www\agent_9art\backend\kb-admin-service\src\main\java\com\artedu\kbadmin\entity\Utterance.java D:\www\agent_9art\backend\kb-admin-service\src\main\java\com\artedu\kbadmin\KbAdminServiceApplication.java +D:\www\agent_9art\backend\kb-admin-service\src\main\java\com\artedu\kbadmin\mapper\AdminMapper.java +D:\www\agent_9art\backend\kb-admin-service\src\main\java\com\artedu\kbadmin\mapper\KnowledgeDocumentMapper.java D:\www\agent_9art\backend\kb-admin-service\src\main\java\com\artedu\kbadmin\mapper\UtteranceMapper.java diff --git a/backend/kb-admin-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/backend/kb-admin-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..1cfd552 --- /dev/null +++ b/backend/kb-admin-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst @@ -0,0 +1,2 @@ +com\artedu\kbadmin\controller\UtteranceControllerTest.class +com\artedu\kbadmin\controller\AuthControllerTest.class diff --git a/backend/kb-admin-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/backend/kb-admin-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..101aed6 --- /dev/null +++ b/backend/kb-admin-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1,2 @@ +D:\www\agent_9art\backend\kb-admin-service\src\test\java\com\artedu\kbadmin\controller\AuthControllerTest.java +D:\www\agent_9art\backend\kb-admin-service\src\test\java\com\artedu\kbadmin\controller\UtteranceControllerTest.java diff --git a/backend/kb-admin-service/target/test-classes/application-test.yml b/backend/kb-admin-service/target/test-classes/application-test.yml new file mode 100644 index 0000000..7da147c --- /dev/null +++ b/backend/kb-admin-service/target/test-classes/application-test.yml @@ -0,0 +1,27 @@ +spring: + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + username: sa + password: + sql: + init: + mode: always + schema-locations: classpath:schema.sql + data-locations: classpath:data.sql + redis: + host: localhost + port: 6379 + +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + logic-delete-field: deleted + logic-delete-value: 1 + logic-not-delete-value: 0 + +jwt: + secret: test-jwt-secret-key-2024-for-unit-testing-only + expiration: 86400 diff --git a/backend/kb-admin-service/target/test-classes/com/artedu/kbadmin/controller/AuthControllerTest.class b/backend/kb-admin-service/target/test-classes/com/artedu/kbadmin/controller/AuthControllerTest.class new file mode 100644 index 0000000..ca9a9d6 Binary files /dev/null and b/backend/kb-admin-service/target/test-classes/com/artedu/kbadmin/controller/AuthControllerTest.class differ diff --git a/backend/kb-admin-service/target/test-classes/com/artedu/kbadmin/controller/UtteranceControllerTest.class b/backend/kb-admin-service/target/test-classes/com/artedu/kbadmin/controller/UtteranceControllerTest.class new file mode 100644 index 0000000..b8f7cb9 Binary files /dev/null and b/backend/kb-admin-service/target/test-classes/com/artedu/kbadmin/controller/UtteranceControllerTest.class differ diff --git a/backend/kb-admin-service/target/test-classes/data.sql b/backend/kb-admin-service/target/test-classes/data.sql new file mode 100644 index 0000000..823b536 --- /dev/null +++ b/backend/kb-admin-service/target/test-classes/data.sql @@ -0,0 +1,6 @@ +INSERT INTO admins (id, username, password, name, email, role, status, created_at, updated_at) VALUES +(1, 'admin', '$2b$10$.jFUiPdJDb/4ksDJWbPdauG2VZjBsG3qJ68DnCQ8YvXyY0UtnpPvW', '系统管理员', 'admin@9artedu.com', 'SUPER_ADMIN', 'ACTIVE', NOW(), NOW()); + +INSERT INTO utterances (utterance_id, corp_id, title, content, content_text, status, priority, stage_tags, intent_tags, profile_tags, success_rate, created_by, source) VALUES +('UTT-001-001', 'corp001', '标准开场白', '您好!欢迎咨询第九联盟CG艺术教育。', '您好!欢迎咨询第九联盟CG艺术教育。', 'ACTIVE', 9, '["STAGE-01"]', '["INT-GREET-01"]', '["通用"]', 0.8205, 'admin', 'MANUAL'), +('UTT-003-001', 'corp001', '零基础推荐-游戏美术', '根据您零基础的情况,我强烈推荐咱们的「游戏美术全能班」。', '根据您零基础的情况,我强烈推荐咱们的「游戏美术全能班」。', 'ACTIVE', 10, '["STAGE-03"]', '["INT-COURSE-01"]', '["零基础"]', 0.8589, 'admin', 'MANUAL'); diff --git a/backend/kb-admin-service/target/test-classes/schema.sql b/backend/kb-admin-service/target/test-classes/schema.sql new file mode 100644 index 0000000..638fb47 --- /dev/null +++ b/backend/kb-admin-service/target/test-classes/schema.sql @@ -0,0 +1,69 @@ +CREATE TABLE IF NOT EXISTS admins ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(64) NOT NULL UNIQUE, + password VARCHAR(256) NOT NULL, + name VARCHAR(64), + email VARCHAR(128), + phone VARCHAR(32), + role VARCHAR(32) DEFAULT 'ADMIN', + status VARCHAR(32) DEFAULT 'ACTIVE', + last_login_time TIMESTAMP, + last_login_ip VARCHAR(64), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS utterances ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + utterance_id VARCHAR(64) NOT NULL UNIQUE, + corp_id VARCHAR(64) NOT NULL, + title VARCHAR(128) NOT NULL, + content TEXT NOT NULL, + content_text TEXT, + version INT DEFAULT 1, + status VARCHAR(32) DEFAULT 'DRAFT', + priority INT DEFAULT 5, + stage_tags TEXT, + intent_tags TEXT, + profile_tags TEXT, + course_type_tags TEXT, + emotion_tags TEXT, + topic_tags TEXT, + used_count INT DEFAULT 0, + selected_count INT DEFAULT 0, + positive_feedback INT DEFAULT 0, + negative_feedback INT DEFAULT 0, + conversion_count INT DEFAULT 0, + success_rate DECIMAL(5,4) DEFAULT 0.0000, + avg_response_time_ms INT, + has_vector TINYINT DEFAULT 0, + vector_updated_at TIMESTAMP, + trigger_keywords TEXT, + variables TEXT, + created_by VARCHAR(64), + reviewed_by VARCHAR(64), + source VARCHAR(32) DEFAULT 'MANUAL', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted INT DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS knowledge_documents ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + doc_id VARCHAR(64) NOT NULL UNIQUE, + corp_id VARCHAR(64) NOT NULL, + title VARCHAR(256) NOT NULL, + content TEXT, + content_text TEXT, + doc_type VARCHAR(32), + category VARCHAR(64), + tags TEXT, + status VARCHAR(32) DEFAULT 'ACTIVE', + has_vector TINYINT DEFAULT 0, + vector_updated_at TIMESTAMP, + created_by VARCHAR(64), + updated_by VARCHAR(64), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted INT DEFAULT 0 +); diff --git a/backend/pom.xml b/backend/pom.xml index 62507e9..70b7de4 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -85,6 +85,12 @@ mysql-connector-java 8.0.33 + + com.h2database + h2 + 2.2.224 + test + diff --git a/backend/recommendation-service/Dockerfile b/backend/recommendation-service/Dockerfile index 858c6fb..5305477 100644 --- a/backend/recommendation-service/Dockerfile +++ b/backend/recommendation-service/Dockerfile @@ -1,5 +1,5 @@ -FROM openjdk:17-jdk-slim +FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY target/*.jar app.jar -EXPOSE 8080 +EXPOSE 8085 ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/backend/recommendation-service/pom.xml b/backend/recommendation-service/pom.xml index 2f44f18..051e237 100644 --- a/backend/recommendation-service/pom.xml +++ b/backend/recommendation-service/pom.xml @@ -15,6 +15,11 @@ com.arteducommon${project.version} org.springframework.bootspring-boot-starter-web org.springframework.bootspring-boot-starter-websocket + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/backend/recommendation-service/src/main/java/com/artedu/recommend/controller/RecommendController.java b/backend/recommendation-service/src/main/java/com/artedu/recommend/controller/RecommendController.java index 8c17067..002ea9d 100644 --- a/backend/recommendation-service/src/main/java/com/artedu/recommend/controller/RecommendController.java +++ b/backend/recommendation-service/src/main/java/com/artedu/recommend/controller/RecommendController.java @@ -9,12 +9,15 @@ import com.artedu.recommend.service.RankingService; import com.artedu.recommend.service.RecallService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import javax.validation.constraints.NotBlank; import java.util.*; import java.util.stream.Collectors; @Slf4j +@Validated @RestController @RequestMapping("/api/v1/recommend") public class RecommendController { @@ -30,12 +33,12 @@ public class RecommendController { @PostMapping("/get") public Result>> recommend( - @RequestParam("sessionId") String sessionId, - @RequestParam("customerId") String customerId, - @RequestParam("staffId") String staffId, - @RequestParam("corpId") String corpId, - @RequestParam("intentCode") String intentCode, - @RequestParam("stage") String stage, + @NotBlank(message = "sessionId不能为空") @RequestParam("sessionId") String sessionId, + @NotBlank(message = "customerId不能为空") @RequestParam("customerId") String customerId, + @NotBlank(message = "staffId不能为空") @RequestParam("staffId") String staffId, + @NotBlank(message = "corpId不能为空") @RequestParam("corpId") String corpId, + @NotBlank(message = "intentCode不能为空") @RequestParam("intentCode") String intentCode, + @NotBlank(message = "stage不能为空") @RequestParam("stage") String stage, @RequestParam(value = "studentType", defaultValue = "通用") String studentType, @RequestParam(value = "message", required = false) String message) { @@ -83,24 +86,28 @@ public class RecommendController { recommendationMapper.insert(rec); // 构建响应 - List> result = ranked.stream().map(u -> { + List> result = new ArrayList<>(); + for (int i = 0; i < ranked.size(); i++) { + Utterance u = ranked.get(i); Map map = new HashMap<>(); map.put("utteranceId", u.getUtteranceId()); map.put("title", u.getTitle()); map.put("content", u.getContent()); + map.put("contentText", u.getContentText()); map.put("successRate", u.getSuccessRate()); map.put("usedCount", u.getUsedCount()); map.put("priority", u.getPriority()); - return map; - }).collect(Collectors.toList()); + map.put("source", i < ruleResults.size() ? "规则匹配" : (i < ruleResults.size() + vectorResults.size() ? "向量召回" : "热门推荐")); + result.add(map); + } return Result.success(result); } @PostMapping("/feedback") public Result feedback( - @RequestParam("recommendationId") String recommendationId, - @RequestParam("action") String action, + @NotBlank(message = "recommendationId不能为空") @RequestParam("recommendationId") String recommendationId, + @NotBlank(message = "action不能为空") @RequestParam("action") String action, @RequestParam(value = "feedback", required = false) String feedback, @RequestParam(value = "reason", required = false) String reason) { diff --git a/backend/recommendation-service/src/main/java/com/artedu/recommend/service/EmbeddingClient.java b/backend/recommendation-service/src/main/java/com/artedu/recommend/service/EmbeddingClient.java new file mode 100644 index 0000000..e1a73fd --- /dev/null +++ b/backend/recommendation-service/src/main/java/com/artedu/recommend/service/EmbeddingClient.java @@ -0,0 +1,84 @@ +package com.artedu.recommend.service; + +import com.artedu.common.util.JsonUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.*; + +@Slf4j +@Service +public class EmbeddingClient { + + @Value("${llm.qianwen.api-key:}") + private String apiKey; + + @Value("${llm.qianwen.embedding-model:text-embedding-v1}") + private String embeddingModel; + + private final RestTemplate restTemplate = new RestTemplate(); + private static final String EMBEDDING_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings"; + + /** + * 获取文本的 Embedding 向量 + */ + public List embed(String text) { + if (text == null || text.trim().isEmpty()) { + return List.of(); + } + try { + Map requestBody = new HashMap<>(); + requestBody.put("model", embeddingModel); + requestBody.put("input", text); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + apiKey); + + HttpEntity> request = new HttpEntity<>(requestBody, headers); + ResponseEntity response = restTemplate.postForEntity(EMBEDDING_URL, request, String.class); + + if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) { + Map result = JsonUtils.fromJsonMap(response.getBody()); + return extractEmbedding(result); + } + log.error("Embedding API 调用失败: status={}, body={}", response.getStatusCode(), response.getBody()); + return List.of(); + } catch (Exception e) { + log.error("Embedding API 异常: {}", e.getMessage()); + return List.of(); + } + } + + /** + * 批量获取 Embedding + */ + public Map> embedBatch(List texts) { + Map> result = new HashMap<>(); + for (String text : texts) { + List vec = embed(text); + if (!vec.isEmpty()) { + result.put(text, vec); + } + } + return result; + } + + @SuppressWarnings("unchecked") + private List extractEmbedding(Map result) { + if (result == null) return List.of(); + try { + List> data = (List>) result.get("data"); + if (data != null && !data.isEmpty()) { + List embedding = (List) data.get(0).get("embedding"); + return embedding != null ? embedding : List.of(); + } + } catch (Exception e) { + log.error("解析 Embedding 失败", e); + } + return List.of(); + } +} diff --git a/backend/recommendation-service/src/main/java/com/artedu/recommend/service/RecallService.java b/backend/recommendation-service/src/main/java/com/artedu/recommend/service/RecallService.java index 1d30e16..94b0b17 100644 --- a/backend/recommendation-service/src/main/java/com/artedu/recommend/service/RecallService.java +++ b/backend/recommendation-service/src/main/java/com/artedu/recommend/service/RecallService.java @@ -10,6 +10,7 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Service; +import javax.annotation.PostConstruct; import java.util.*; import java.util.stream.Collectors; @@ -23,15 +24,58 @@ public class RecallService { @Autowired private RedisTemplate redisTemplate; + @Autowired + private EmbeddingClient embeddingClient; + private static final String VECTOR_KEY_PREFIX = "utterance:vector:"; private static final String HOT_KEY = "utterance:hot"; + /** + * 应用启动时预计算所有话术的 Embedding + */ + @PostConstruct + public void initVectors() { + log.info("开始预计算话术 Embedding..."); + try { + List all = utteranceMapper.selectList( + new LambdaQueryWrapper() + .eq(Utterance::getStatus, "ACTIVE") + ); + int cached = 0; + for (Utterance u : all) { + String key = VECTOR_KEY_PREFIX + u.getUtteranceId(); + if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) { + continue; + } + String text = u.getContentText() != null ? u.getContentText() : u.getContent(); + if (text == null || text.trim().isEmpty()) continue; + + List vector = embeddingClient.embed(text); + if (!vector.isEmpty()) { + redisTemplate.opsForValue().set(key, JsonUtils.toJson(vector)); + cached++; + } + } + log.info("Embedding 预计算完成, 新缓存: {}/{}, 模型: text-embedding-v1, 维度: 1536", + cached, all.size()); + } catch (Exception e) { + log.error("Embedding 预计算失败: {}", e.getMessage()); + } + } + public List ruleRecall(String intentCode, String stage, String studentType, String corpId) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Utterance::getCorpId, corpId) .eq(Utterance::getStatus, "ACTIVE"); List all = utteranceMapper.selectList(wrapper); + if (all == null || all.isEmpty()) { + log.warn("corpId={} 无匹配话术,回退到通用查询", corpId); + wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Utterance::getStatus, "ACTIVE"); + all = utteranceMapper.selectList(wrapper); + } + return all.stream().filter(u -> { String intentTagsStr = u.getIntentTags(); String stageTagsStr = u.getStageTags(); @@ -49,41 +93,66 @@ public class RecallService { .collect(Collectors.toList()); } - public List vectorRecall(String messageEmbedding, String corpId) { - // 简化实现:通过Redis存储的向量进行余弦相似度计算 - // 实际项目中应使用向量数据库或更高效的相似度计算 + public List vectorRecall(String message, String corpId) { + if (message == null || message.trim().isEmpty()) { + return List.of(); + } + + // 1. 获取用户消息的 Embedding + List msgVector = embeddingClient.embed(message); + if (msgVector.isEmpty()) { + log.warn("消息 Embedding 获取失败,回退到热门口话术"); + return hotRecall(corpId); + } + + // 2. 从 Redis 获取所有话术向量并计算相似度 Set keys = redisTemplate.keys(VECTOR_KEY_PREFIX + "*"); if (keys == null || keys.isEmpty()) { - return List.of(); + log.warn("Redis 中无话术向量,回退到热门口话术"); + return hotRecall(corpId); } Map similarityMap = new HashMap<>(); for (String key : keys) { - String vectorStr = (String) redisTemplate.opsForValue().get(key); - if (vectorStr != null) { - double sim = cosineSimilarity(messageEmbedding, vectorStr); - if (sim > 0.7) { - similarityMap.put(key.replace(VECTOR_KEY_PREFIX, ""), sim); - } + String vectorJson = (String) redisTemplate.opsForValue().get(key); + if (vectorJson == null || vectorJson.isEmpty()) continue; + + List utteranceVector = JsonUtils.fromJsonList(vectorJson, Double.class); + if (utteranceVector == null || utteranceVector.isEmpty()) continue; + + double sim = cosineSimilarity(msgVector, utteranceVector); + if (sim > 0.75) { + String utteranceId = key.replace(VECTOR_KEY_PREFIX, ""); + similarityMap.put(utteranceId, sim); } } + if (similarityMap.isEmpty()) { + log.info("向量相似度均低于阈值,回退到热门口话术"); + return hotRecall(corpId); + } + + // 3. 按相似度排序取 Top10 List utteranceIds = similarityMap.entrySet().stream() .sorted(Map.Entry.comparingByValue().reversed()) .limit(10) .map(Map.Entry::getKey) .collect(Collectors.toList()); - if (utteranceIds.isEmpty()) { - return List.of(); - } - - return utteranceMapper.selectList( + List result = utteranceMapper.selectList( new LambdaQueryWrapper() .eq(Utterance::getCorpId, corpId) .in(Utterance::getUtteranceId, utteranceIds) .eq(Utterance::getStatus, "ACTIVE") ); + if (result == null || result.isEmpty()) { + result = utteranceMapper.selectList( + new LambdaQueryWrapper() + .in(Utterance::getUtteranceId, utteranceIds) + .eq(Utterance::getStatus, "ACTIVE") + ); + } + return result; } public List hotRecall(String corpId) { @@ -91,30 +160,68 @@ public class RecallService { .reverseRangeWithScores(HOT_KEY + ":" + corpId, 0, 9); if (hotSet == null || hotSet.isEmpty()) { - return utteranceMapper.selectList( + List result = utteranceMapper.selectList( new LambdaQueryWrapper() .eq(Utterance::getCorpId, corpId) .eq(Utterance::getStatus, "ACTIVE") .orderByDesc(Utterance::getSuccessRate) .last("LIMIT 10") ); + if (result == null || result.isEmpty()) { + result = utteranceMapper.selectList( + new LambdaQueryWrapper() + .eq(Utterance::getStatus, "ACTIVE") + .orderByDesc(Utterance::getSuccessRate) + .last("LIMIT 10") + ); + } + return result; } List utteranceIds = hotSet.stream() .map(t -> (String) t.getValue()) .collect(Collectors.toList()); - return utteranceMapper.selectList( + List result = utteranceMapper.selectList( new LambdaQueryWrapper() .eq(Utterance::getCorpId, corpId) .in(Utterance::getUtteranceId, utteranceIds) .eq(Utterance::getStatus, "ACTIVE") ); + if (result == null || result.isEmpty()) { + result = utteranceMapper.selectList( + new LambdaQueryWrapper() + .in(Utterance::getUtteranceId, utteranceIds) + .eq(Utterance::getStatus, "ACTIVE") + ); + } + return result; } - private double cosineSimilarity(String v1, String v2) { - // 简化实现:实际应解析向量并计算余弦相似度 - // 这里使用随机值作为占位 - return 0.5 + Math.random() * 0.5; + /** + * 计算两个向量的余弦相似度 + */ + private double cosineSimilarity(List v1, List v2) { + if (v1 == null || v2 == null || v1.size() != v2.size() || v1.isEmpty()) { + return 0.0; + } + + double dotProduct = 0.0; + double norm1 = 0.0; + double norm2 = 0.0; + + for (int i = 0; i < v1.size(); i++) { + double d1 = v1.get(i); + double d2 = v2.get(i); + dotProduct += d1 * d2; + norm1 += d1 * d1; + norm2 += d2 * d2; + } + + if (norm1 == 0.0 || norm2 == 0.0) { + return 0.0; + } + + return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2)); } } diff --git a/backend/recommendation-service/src/main/resources/application.yml b/backend/recommendation-service/src/main/resources/application.yml index 3589666..b7ea577 100644 --- a/backend/recommendation-service/src/main/resources/application.yml +++ b/backend/recommendation-service/src/main/resources/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8085 spring: @@ -6,7 +11,7 @@ spring: name: recommendation-service datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false + url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false username: ${MYSQL_USERNAME:root} password: ${MYSQL_PASSWORD:root} redis: @@ -25,6 +30,11 @@ spring: username: ${RABBITMQ_USERNAME:admin} password: ${RABBITMQ_PASSWORD:admin} +llm: + qianwen: + api-key: ${QIANWEN_API_KEY:sk-39ceb8d746014b349109e76f893acb18} + embedding-model: text-embedding-v1 + mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl diff --git a/backend/recommendation-service/src/test/java/com/artedu/recommend/service/RecallServiceTest.java b/backend/recommendation-service/src/test/java/com/artedu/recommend/service/RecallServiceTest.java new file mode 100644 index 0000000..f028b55 --- /dev/null +++ b/backend/recommendation-service/src/test/java/com/artedu/recommend/service/RecallServiceTest.java @@ -0,0 +1,151 @@ +package com.artedu.recommend.service; + +import com.artedu.recommend.entity.Utterance; +import com.artedu.recommend.mapper.UtteranceMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@SpringBootTest(properties = { + "spring.data.redis.repositories.enabled=false", + "spring.redis.host=localhost", + "spring.redis.port=6379" +}) +public class RecallServiceTest { + + @Autowired + private RecallService recallService; + + @MockBean + private UtteranceMapper utteranceMapper; + + @MockBean + private RedisTemplate redisTemplate; + + @MockBean + private EmbeddingClient embeddingClient; + + @MockBean + private ValueOperations valueOps; + + @BeforeEach + void setup() { + when(redisTemplate.opsForValue()).thenReturn(valueOps); + } + + @Test + void testRuleRecall_MatchAll() { + Utterance u1 = createUtterance("UTT-001", "ACTIVE", 10, + "[\"INT-COURSE-01\"]", "[\"STAGE-03\"]", "[\"零基础\"]"); + + when(utteranceMapper.selectList(any(LambdaQueryWrapper.class))) + .thenReturn(List.of(u1)); + + List result = recallService.ruleRecall( + "INT-COURSE-01", "STAGE-03", "零基础", "corp001"); + + assertEquals(1, result.size()); + assertEquals("UTT-001", result.get(0).getUtteranceId()); + } + + @Test + void testRuleRecall_NoMatch() { + Utterance u1 = createUtterance("UTT-001", "ACTIVE", 10, + "[\"INT-OTHER\"]", "[\"STAGE-01\"]", "[\"有基础\"]"); + + when(utteranceMapper.selectList(any(LambdaQueryWrapper.class))) + .thenReturn(List.of(u1)); + + List result = recallService.ruleRecall( + "INT-COURSE-01", "STAGE-03", "零基础", "corp001"); + + assertTrue(result.isEmpty()); + } + + @Test + void testRuleRecall_FallbackWhenCorpIdEmpty() { + when(utteranceMapper.selectList(any(LambdaQueryWrapper.class))) + .thenReturn(List.of()) + .thenReturn(List.of( + createUtterance("UTT-001", "ACTIVE", 10, + "[\"INT-COURSE-01\"]", "[\"STAGE-03\"]", "[\"通用\"]") + )); + + List result = recallService.ruleRecall( + "INT-COURSE-01", "STAGE-03", "通用", "unknown-corp"); + + assertEquals(1, result.size()); + } + + @Test + void testCosineSimilarity_IdenticalVectors() { + double sim = invokeCosineSimilarity( + Arrays.asList(1.0, 0.0, 0.0), + Arrays.asList(1.0, 0.0, 0.0) + ); + assertEquals(1.0, sim, 0.0001); + } + + @Test + void testCosineSimilarity_OrthogonalVectors() { + double sim = invokeCosineSimilarity( + Arrays.asList(1.0, 0.0), + Arrays.asList(0.0, 1.0) + ); + assertEquals(0.0, sim, 0.0001); + } + + @Test + void testCosineSimilarity_OppositeVectors() { + double sim = invokeCosineSimilarity( + Arrays.asList(1.0, 0.0), + Arrays.asList(-1.0, 0.0) + ); + assertEquals(-1.0, sim, 0.0001); + } + + @Test + void testCosineSimilarity_DifferentSize() { + double sim = invokeCosineSimilarity( + Arrays.asList(1.0, 0.0), + Arrays.asList(1.0, 0.0, 0.0) + ); + assertEquals(0.0, sim, 0.0001); + } + + private double invokeCosineSimilarity(List v1, List v2) { + try { + java.lang.reflect.Method method = RecallService.class.getDeclaredMethod( + "cosineSimilarity", List.class, List.class); + method.setAccessible(true); + return (Double) method.invoke(recallService, v1, v2); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private Utterance createUtterance(String id, String status, int priority, + String intentTags, String stageTags, String profileTags) { + Utterance u = new Utterance(); + u.setUtteranceId(id); + u.setStatus(status); + u.setPriority(priority); + u.setIntentTags(intentTags); + u.setStageTags(stageTags); + u.setProfileTags(profileTags); + u.setCorpId("corp001"); + return u; + } +} diff --git a/backend/recommendation-service/target/classes/application.yml b/backend/recommendation-service/target/classes/application.yml index 96f04b4..b7ea577 100644 --- a/backend/recommendation-service/target/classes/application.yml +++ b/backend/recommendation-service/target/classes/application.yml @@ -1,4 +1,9 @@ server: + servlet: + encoding: + charset: UTF-8 + enabled: true + force: true port: 8085 spring: @@ -25,6 +30,11 @@ spring: username: ${RABBITMQ_USERNAME:admin} password: ${RABBITMQ_PASSWORD:admin} +llm: + qianwen: + api-key: ${QIANWEN_API_KEY:sk-39ceb8d746014b349109e76f893acb18} + embedding-model: text-embedding-v1 + mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl diff --git a/backend/recommendation-service/target/classes/com/artedu/recommend/RecommendationServiceApplication.class b/backend/recommendation-service/target/classes/com/artedu/recommend/RecommendationServiceApplication.class index cd3d669..79bc2e5 100644 Binary files a/backend/recommendation-service/target/classes/com/artedu/recommend/RecommendationServiceApplication.class and b/backend/recommendation-service/target/classes/com/artedu/recommend/RecommendationServiceApplication.class differ diff --git a/backend/recommendation-service/target/classes/com/artedu/recommend/controller/RecommendController.class b/backend/recommendation-service/target/classes/com/artedu/recommend/controller/RecommendController.class index 6102bcf..60de04b 100644 Binary files a/backend/recommendation-service/target/classes/com/artedu/recommend/controller/RecommendController.class and b/backend/recommendation-service/target/classes/com/artedu/recommend/controller/RecommendController.class differ diff --git a/backend/recommendation-service/target/classes/com/artedu/recommend/entity/Recommendation.class b/backend/recommendation-service/target/classes/com/artedu/recommend/entity/Recommendation.class index da0620f..f026bbe 100644 Binary files a/backend/recommendation-service/target/classes/com/artedu/recommend/entity/Recommendation.class and b/backend/recommendation-service/target/classes/com/artedu/recommend/entity/Recommendation.class differ diff --git a/backend/recommendation-service/target/classes/com/artedu/recommend/entity/Utterance.class b/backend/recommendation-service/target/classes/com/artedu/recommend/entity/Utterance.class index 5beb649..f0190ba 100644 Binary files a/backend/recommendation-service/target/classes/com/artedu/recommend/entity/Utterance.class and b/backend/recommendation-service/target/classes/com/artedu/recommend/entity/Utterance.class differ diff --git a/backend/recommendation-service/target/classes/com/artedu/recommend/mapper/RecommendationMapper.class b/backend/recommendation-service/target/classes/com/artedu/recommend/mapper/RecommendationMapper.class index 73b1ad9..956e760 100644 Binary files a/backend/recommendation-service/target/classes/com/artedu/recommend/mapper/RecommendationMapper.class and b/backend/recommendation-service/target/classes/com/artedu/recommend/mapper/RecommendationMapper.class differ diff --git a/backend/recommendation-service/target/classes/com/artedu/recommend/mapper/UtteranceMapper.class b/backend/recommendation-service/target/classes/com/artedu/recommend/mapper/UtteranceMapper.class index c7754e0..9b8d098 100644 Binary files a/backend/recommendation-service/target/classes/com/artedu/recommend/mapper/UtteranceMapper.class and b/backend/recommendation-service/target/classes/com/artedu/recommend/mapper/UtteranceMapper.class differ diff --git a/backend/recommendation-service/target/classes/com/artedu/recommend/service/EmbeddingClient.class b/backend/recommendation-service/target/classes/com/artedu/recommend/service/EmbeddingClient.class new file mode 100644 index 0000000..80bd616 Binary files /dev/null and b/backend/recommendation-service/target/classes/com/artedu/recommend/service/EmbeddingClient.class differ diff --git a/backend/recommendation-service/target/classes/com/artedu/recommend/service/RankingService$ScoredUtterance.class b/backend/recommendation-service/target/classes/com/artedu/recommend/service/RankingService$ScoredUtterance.class index 26dc80e..500b8b2 100644 Binary files a/backend/recommendation-service/target/classes/com/artedu/recommend/service/RankingService$ScoredUtterance.class and b/backend/recommendation-service/target/classes/com/artedu/recommend/service/RankingService$ScoredUtterance.class differ diff --git a/backend/recommendation-service/target/classes/com/artedu/recommend/service/RankingService.class b/backend/recommendation-service/target/classes/com/artedu/recommend/service/RankingService.class index 2345fa2..1339f28 100644 Binary files a/backend/recommendation-service/target/classes/com/artedu/recommend/service/RankingService.class and b/backend/recommendation-service/target/classes/com/artedu/recommend/service/RankingService.class differ diff --git a/backend/recommendation-service/target/classes/com/artedu/recommend/service/RecallService.class b/backend/recommendation-service/target/classes/com/artedu/recommend/service/RecallService.class index 3d81ffe..278bac7 100644 Binary files a/backend/recommendation-service/target/classes/com/artedu/recommend/service/RecallService.class and b/backend/recommendation-service/target/classes/com/artedu/recommend/service/RecallService.class differ diff --git a/backend/recommendation-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/backend/recommendation-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index 9a392f0..bd9ac93 100644 --- a/backend/recommendation-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/backend/recommendation-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -4,6 +4,7 @@ com\artedu\recommend\entity\Recommendation.class com\artedu\recommend\entity\Utterance.class com\artedu\recommend\mapper\RecommendationMapper.class com\artedu\recommend\service\RankingService.class +com\artedu\recommend\service\EmbeddingClient.class com\artedu\recommend\service\RecallService.class com\artedu\recommend\mapper\UtteranceMapper.class com\artedu\recommend\service\RankingService$ScoredUtterance.class diff --git a/backend/recommendation-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/backend/recommendation-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index 19c190b..c771475 100644 --- a/backend/recommendation-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/backend/recommendation-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -4,5 +4,6 @@ D:\www\agent_9art\backend\recommendation-service\src\main\java\com\artedu\recomm D:\www\agent_9art\backend\recommendation-service\src\main\java\com\artedu\recommend\mapper\RecommendationMapper.java D:\www\agent_9art\backend\recommendation-service\src\main\java\com\artedu\recommend\mapper\UtteranceMapper.java D:\www\agent_9art\backend\recommendation-service\src\main\java\com\artedu\recommend\RecommendationServiceApplication.java +D:\www\agent_9art\backend\recommendation-service\src\main\java\com\artedu\recommend\service\EmbeddingClient.java D:\www\agent_9art\backend\recommendation-service\src\main\java\com\artedu\recommend\service\RankingService.java D:\www\agent_9art\backend\recommendation-service\src\main\java\com\artedu\recommend\service\RecallService.java diff --git a/backend/recommendation-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/backend/recommendation-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..f63474c --- /dev/null +++ b/backend/recommendation-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst @@ -0,0 +1 @@ +com\artedu\recommend\service\RecallServiceTest.class diff --git a/backend/recommendation-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/backend/recommendation-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..aaf8558 --- /dev/null +++ b/backend/recommendation-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1 @@ +D:\www\agent_9art\backend\recommendation-service\src\test\java\com\artedu\recommend\service\RecallServiceTest.java diff --git a/backend/recommendation-service/target/recommendation-service-1.0.0-SNAPSHOT.jar b/backend/recommendation-service/target/recommendation-service-1.0.0-SNAPSHOT.jar index ce226f4..1f32470 100644 Binary files a/backend/recommendation-service/target/recommendation-service-1.0.0-SNAPSHOT.jar and b/backend/recommendation-service/target/recommendation-service-1.0.0-SNAPSHOT.jar differ diff --git a/backend/recommendation-service/target/recommendation-service-1.0.0-SNAPSHOT.jar.original b/backend/recommendation-service/target/recommendation-service-1.0.0-SNAPSHOT.jar.original new file mode 100644 index 0000000..95367a2 Binary files /dev/null and b/backend/recommendation-service/target/recommendation-service-1.0.0-SNAPSHOT.jar.original differ diff --git a/backend/recommendation-service/target/test-classes/com/artedu/recommend/service/RecallServiceTest.class b/backend/recommendation-service/target/test-classes/com/artedu/recommend/service/RecallServiceTest.class new file mode 100644 index 0000000..bc2b86c Binary files /dev/null and b/backend/recommendation-service/target/test-classes/com/artedu/recommend/service/RecallServiceTest.class differ diff --git a/backend/utterances_cg.sql b/backend/utterances_cg.sql new file mode 100644 index 0000000..74b7e60 --- /dev/null +++ b/backend/utterances_cg.sql @@ -0,0 +1,245 @@ +SET NAMES utf8mb4; +USE ai_assistant; + +INSERT INTO utterances +(utterance_id, corp_id, title, content, content_text, version, status, priority, + stage_tags, intent_tags, profile_tags, course_type_tags, emotion_tags, topic_tags, + used_count, selected_count, success_rate, has_vector, created_by, source) +VALUES +('UTT-001-001', 'corp001', '标准开场白-首次咨询', + '您好!欢迎咨询第九联盟CG艺术教育,我是您的专属课程顾问{{顾问姓名}}。请问怎么称呼您呢?是想了解咱们哪方面的课程信息呢?', + '您好!欢迎咨询第九联盟CG艺术教育,我是您的专属课程顾问。请问怎么称呼您呢?是想了解咱们哪方面的课程信息呢?', + 1, 'ACTIVE', 9, + '["STAGE-01"]', '["INT-GREET-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["FRIENDLY"]', '["开场","问候"]', + 156, 128, 0.8205, 0, 'admin', 'MANUAL'), + +('UTT-001-002', 'corp001', '转介绍开场白', + '您好{{学员姓名}}!我是第九联盟的{{顾问姓名}},听说是{{推荐人姓名}}推荐您来咨询我们的课程?咱们关系这么好,我一定给您安排最合适的课程方案!', + '您好!我是第九联盟的顾问,听说是朋友推荐您来咨询我们的课程?咱们关系这么好,我一定给您安排最合适的课程方案!', + 1, 'ACTIVE', 8, + '["STAGE-01"]', '["INT-GREET-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["FRIENDLY"]', '["开场","转介绍"]', + 89, 72, 0.8090, 0, 'admin', 'MANUAL'), + +('UTT-001-003', 'corp001', '活动咨询开场白', + '您好!看到您关注了我们这次的{{活动名称}}优惠活动,现在报名确实是非常划算的!我是课程顾问{{顾问姓名}},先简单了解一下您的情况,帮您看看能享受哪些优惠?', + '您好!看到您关注了我们这次的优惠活动,现在报名确实是非常划算的!我是课程顾问,先简单了解一下您的情况,帮您看看能享受哪些优惠?', + 1, 'ACTIVE', 8, + '["STAGE-01"]', '["INT-GREET-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["EXCITED"]', '["开场","活动"]', + 112, 95, 0.8482, 0, 'admin', 'MANUAL'), + +('UTT-001-004', 'corp001', '回访开场白', + '{{学员姓名}}您好!我是第九联盟的{{顾问姓名}},之前跟您聊过CG课程的事。最近考虑得怎么样啦?有没有新的问题想了解的?', + '您好!我是第九联盟的顾问,之前跟您聊过CG课程的事。最近考虑得怎么样啦?有没有新的问题想了解的?', + 1, 'ACTIVE', 7, + '["STAGE-01"]', '["INT-GREET-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["FRIENDLY"]', '["开场","回访"]', + 78, 62, 0.7949, 0, 'admin', 'MANUAL'), + +('UTT-002-001', 'corp001', '了解学员背景-在校生', + '{{学员姓名}},方便问一下您现在是在校大学生,还是已经工作了呀?如果是在校生的话,咱们可以利用寒暑假集中学习,毕业后直接入行,这个时间规划是非常合理的。', + '方便问一下您现在是在校大学生,还是已经工作了呀?如果是在校生的话,咱们可以利用寒暑假集中学习,毕业后直接入行,这个时间规划是非常合理的。', + 1, 'ACTIVE', 9, + '["STAGE-02"]', '["INT-NEED-01"]', '["在校生"]', '["GAME_ART","3D_MODEL","CONCEPT_ART"]', '["NEUTRAL"]', '["需求探询","背景"]', + 198, 165, 0.8333, 0, 'admin', 'MANUAL'), + +('UTT-002-002', 'corp001', '了解学员背景-转行人员', + '理解您想转行的想法!很多来咨询的同学都是看中了CG行业的高薪前景。您目前从事的是什么行业呢?转行到游戏美术/影视后期方向,零基础大概6-8个月就能达到就业水平。', + '理解您想转行的想法!很多来咨询的同学都是看中了CG行业的高薪前景。您目前从事的是什么行业呢?转行到游戏美术/影视后期方向,零基础大概6-8个月就能达到就业水平。', + 1, 'ACTIVE', 9, + '["STAGE-02"]', '["INT-NEED-01"]', '["转行人员"]', '["GAME_ART","3D_MODEL","VFX"]', '["NEUTRAL"]', '["需求探询","转行"]', + 245, 198, 0.8082, 0, 'admin', 'MANUAL'), + +('UTT-002-003', 'corp001', '了解学习目标', + '那您学习CG课程的主要目标是什么?是为了找到一份高薪工作,还是兴趣爱好,或者是在现有岗位上提升技能?不同目标对应的课程路径差别还是挺大的。', + '那您学习CG课程的主要目标是什么?是为了找到一份高薪工作,还是兴趣爱好,或者是在现有岗位上提升技能?不同目标对应的课程路径差别还是挺大的。', + 1, 'ACTIVE', 8, + '["STAGE-02"]', '["INT-NEED-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["NEUTRAL"]', '["需求探询","目标"]', + 178, 142, 0.7978, 0, 'admin', 'MANUAL'), + +('UTT-002-004', 'corp001', '了解美术基础', + '您之前有接触过绘画或者设计相关的学习吗?比如素描、色彩、PS这些。有基础的话学习3D建模和原画会更快上手,完全零基础也没关系,我们的课程就是从最基础的美术功底开始教起的。', + '您之前有接触过绘画或者设计相关的学习吗?比如素描、色彩、PS这些。有基础的话学习3D建模和原画会更快上手,完全零基础也没关系,我们的课程就是从最基础的美术功底开始教起的。', + 1, 'ACTIVE', 8, + '["STAGE-02"]', '["INT-NEED-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART"]', '["NEUTRAL"]', '["需求探询","基础"]', + 156, 128, 0.8205, 0, 'admin', 'MANUAL'), + +('UTT-002-005', 'corp001', '了解时间安排', + '您平时的时间安排是怎样的?我们可以根据您的时间来推荐最合适的班型。全日制班是周一到周五上课,适合想要快速就业的同学;周末班和线上班适合在职人员边工作边学习。', + '您平时的时间安排是怎样的?我们可以根据您的时间来推荐最合适的班型。全日制班是周一到周五上课,适合想要快速就业的同学;周末班和线上班适合在职人员边工作边学习。', + 1, 'ACTIVE', 7, + '["STAGE-02"]', '["INT-NEED-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["NEUTRAL"]', '["需求探询","时间"]', + 134, 108, 0.8059, 0, 'admin', 'MANUAL'), + +('UTT-003-001', 'corp001', '零基础推荐-游戏美术', + '根据您零基础的情况,我强烈推荐咱们的「游戏美术全能班」。这个班从PS手绘基础开始,逐步学习3D建模、贴图绘制、引擎渲染,8个月学完后可以直接入行做游戏美术师。咱们往期学员平均就业薪资在8K-12K。', + '根据您零基础的情况,我强烈推荐咱们的「游戏美术全能班」。这个班从PS手绘基础开始,逐步学习3D建模、贴图绘制、引擎渲染,8个月学完后可以直接入行做游戏美术师。咱们往期学员平均就业薪资在8K-12K。', + 1, 'ACTIVE', 10, + '["STAGE-03"]', '["INT-COURSE-01"]', '["零基础"]', '["GAME_ART","3D_MODEL"]', '["EXCITED"]', '["课程推荐","游戏美术"]', + 312, 268, 0.8589, 0, 'admin', 'MANUAL'), + +('UTT-003-002', 'corp001', '零基础推荐-影视后期', + '如果您对影视方向更感兴趣,可以看看「影视后期特效班」。课程涵盖PR剪辑、AE特效、C4D三维动画、NUKE合成,学成后可以进影视公司、广告公司、MCN机构,薪资前景也很不错。', + '如果您对影视方向更感兴趣,可以看看「影视后期特效班」。课程涵盖PR剪辑、AE特效、C4D三维动画、NUKE合成,学成后可以进影视公司、广告公司、MCN机构,薪资前景也很不错。', + 1, 'ACTIVE', 9, + '["STAGE-03"]', '["INT-COURSE-01"]', '["零基础"]', '["VFX","VIDEO_EDIT"]', '["EXCITED"]', '["课程推荐","影视后期"]', + 198, 162, 0.8182, 0, 'admin', 'MANUAL'), + +('UTT-003-003', 'corp001', '有基础推荐-进阶提升', + '您已经有美术基础了,那学习周期可以大大缩短!我建议您直接报「3D建模进阶班」或者「原画设计精修班」,跳过基础部分,重点攻克项目实战。大概4-6个月就能做出高质量作品集。', + '您已经有美术基础了,那学习周期可以大大缩短!我建议您直接报「3D建模进阶班」或者「原画设计精修班」,跳过基础部分,重点攻克项目实战。大概4-6个月就能做出高质量作品集。', + 1, 'ACTIVE', 9, + '["STAGE-03"]', '["INT-COURSE-01"]', '["有基础"]', '["3D_MODEL","CONCEPT_ART"]', '["EXCITED"]', '["课程推荐","进阶"]', + 156, 132, 0.8462, 0, 'admin', 'MANUAL'), + +('UTT-003-004', 'corp001', '转行推荐-快速就业班', + '针对您转行的需求,我们专门设计了「CG行业速成就业班」。6个月全日制集训,从软件操作到项目实战全覆盖,结课后直接内推到合作企业。去年这个班的就业率是97%,平均薪资10K+。', + '针对您转行的需求,我们专门设计了「CG行业速成就业班」。6个月全日制集训,从软件操作到项目实战全覆盖,结课后直接内推到合作企业。去年这个班的就业率是97%,平均薪资10K+。', + 1, 'ACTIVE', 10, + '["STAGE-03"]', '["INT-COURSE-01"]', '["转行人员"]', '["GAME_ART","3D_MODEL","VFX"]', '["EXCITED"]', '["课程推荐","转行","就业"]', + 278, 235, 0.8453, 0, 'admin', 'MANUAL'), + +('UTT-003-005', 'corp001', '热门方向推荐-虚幻引擎', + '最近虚幻引擎(UE5)方向特别火!游戏、影视、虚拟制片、数字孪生都在大量招聘UE人才。我们的「虚幻引擎开发班」学完能做地编、灯光、蓝图交互,薪资比普通美术岗高30%以上,建议您重点考虑一下。', + '最近虚幻引擎(UE5)方向特别火!游戏、影视、虚拟制片、数字孪生都在大量招聘UE人才。我们的「虚幻引擎开发班」学完能做地编、灯光、蓝图交互,薪资比普通美术岗高30%以上,建议您重点考虑一下。', + 1, 'ACTIVE', 10, + '["STAGE-03"]', '["INT-COURSE-01"]', '["通用"]', '["GAME_ART","VFX","UE5"]', '["EXCITED"]', '["课程推荐","虚幻引擎","热门"]', + 356, 298, 0.8371, 0, 'admin', 'MANUAL'), + +('UTT-004-001', 'corp001', '师资力量-一线项目经验', + '咱们的讲师不是那种只会讲课的学院派,全部来自一线游戏公司和影视大厂,比如腾讯、网易、米哈游、追光动画。他们带来的都是真实项目流程和行业最新标准,学的东西直接对标企业需求。', + '咱们的讲师不是那种只会讲课的学院派,全部来自一线游戏公司和影视大厂,比如腾讯、网易、米哈游、追光动画。他们带来的都是真实项目流程和行业最新标准,学的东西直接对标企业需求。', + 1, 'ACTIVE', 9, + '["STAGE-04"]', '["INT-VALUE-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["PROUD"]', '["价值塑造","师资"]', + 198, 168, 0.8485, 0, 'admin', 'MANUAL'), + +('UTT-004-002', 'corp001', '教学模式-项目实战', + '我们的课程70%都是项目实战!不是让你照着教程做,而是直接参与真实商业项目的制作。每个学员毕业时都有5-8个完整作品集,面试的时候直接拿作品说话,企业特别认可这种实战经验。', + '我们的课程70%都是项目实战!不是让你照着教程做,而是直接参与真实商业项目的制作。每个学员毕业时都有5-8个完整作品集,面试的时候直接拿作品说话,企业特别认可这种实战经验。', + 1, 'ACTIVE', 9, + '["STAGE-04"]', '["INT-VALUE-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["PROUD"]', '["价值塑造","实战"]', + 245, 208, 0.8490, 0, 'admin', 'MANUAL'), + +('UTT-004-003', 'corp001', '就业保障-合作企业', + '第九联盟跟国内300多家CG企业建立了人才合作,包括完美世界、莉莉丝、叠纸、原力动画等。结课后我们有专门的就业老师一对一内推,不是随便给你丢个招聘链接,而是直接把简历送到HR手里。', + '第九联盟跟国内300多家CG企业建立了人才合作,包括完美世界、莉莉丝、叠纸、原力动画等。结课后我们有专门的就业老师一对一内推,不是随便给你丢个招聘链接,而是直接把简历送到HR手里。', + 1, 'ACTIVE', 10, + '["STAGE-04"]', '["INT-VALUE-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["PROUD"]', '["价值塑造","就业"]', + 312, 268, 0.8589, 0, 'admin', 'MANUAL'), + +('UTT-004-004', 'corp001', '学习成果-作品集', + '学CG最重要的是作品集!我们的课程每一阶段都会产出可以放进作品集的项目。毕业的时候,您会有一整套从角色设计到场景搭建的完整作品,这个作品集就是您敲开名企大门的敲门砖。', + '学CG最重要的是作品集!我们的课程每一阶段都会产出可以放进作品集的项目。毕业的时候,您会有一整套从角色设计到场景搭建的完整作品,这个作品集就是您敲开名企大门的敲门砖。', + 1, 'ACTIVE', 8, + '["STAGE-04"]', '["INT-VALUE-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["PROUD"]', '["价值塑造","作品集"]', + 178, 148, 0.8315, 0, 'admin', 'MANUAL'), + +('UTT-005-001', 'corp001', '全日制班报价', + '咱们「游戏美术全能班」全日制8个月的学费是{{学费金额}}元。您算一下,8个月学成后入行薪资按8K算,一年就是9.6万,这投资回报是非常划算的。而且现在报名可以减免2000元学费。', + '咱们「游戏美术全能班」全日制8个月的学费是29800元。您算一下,8个月学成后入行薪资按8K算,一年就是9.6万,这投资回报是非常划算的。而且现在报名可以减免2000元学费。', + 1, 'ACTIVE', 8, + '["STAGE-05"]', '["INT-PRICE-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["NEUTRAL"]', '["报价","全日制"]', + 198, 162, 0.8182, 0, 'admin', 'MANUAL'), + +('UTT-005-002', 'corp001', '分期付款方案', + '如果一次性付款有压力,咱们支持12期免息分期,每个月只要还2000多块钱,比很多培训机构的利息低多了。这样您一边学习一边还款,经济压力小很多。', + '如果一次性付款有压力,咱们支持12期免息分期,每个月只要还2000多块钱,比很多培训机构的利息低多了。这样您一边学习一边还款,经济压力小很多。', + 1, 'ACTIVE', 9, + '["STAGE-05"]', '["INT-PRICE-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["FRIENDLY"]', '["报价","分期"]', + 245, 205, 0.8367, 0, 'admin', 'MANUAL'), + +('UTT-005-003', 'corp001', '优惠活动说明', + '这个月正好是我们的周年庆活动期,报名立减3000元,还送价值2000元的Wacom手绘板!这个优惠过了这个月就没有了,建议您尽快做决定。我先把优惠券给您预留上?', + '这个月正好是我们的周年庆活动期,报名立减3000元,还送价值2000元的Wacom手绘板!这个优惠过了这个月就没有了,建议您尽快做决定。我先把优惠券给您预留上?', + 1, 'ACTIVE', 9, + '["STAGE-05"]', '["INT-PRICE-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["EXCITED"]', '["报价","优惠"]', + 278, 238, 0.8561, 0, 'admin', 'MANUAL'), + +('UTT-005-004', 'corp001', '短期班报价', + '如果预算有限或者想先体验一下,可以先报我们的「CG入门体验营」,只要{{体验营价格}}元,2周时间学会基础操作,做出第一个作品。体验完觉得合适再转正式班,体验营费用还可以抵扣正式班学费。', + '如果预算有限或者想先体验一下,可以先报我们的「CG入门体验营」,只要1999元,2周时间学会基础操作,做出第一个作品。体验完觉得合适再转正式班,体验营费用还可以抵扣正式班学费。', + 1, 'ACTIVE', 7, + '["STAGE-05"]', '["INT-PRICE-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["FRIENDLY"]', '["报价","体验营"]', + 156, 128, 0.8205, 0, 'admin', 'MANUAL'), + +('UTT-006-001', 'corp001', '担心学不会-零基础保障', + '我完全理解您的担心!其实90%来报名的同学都是零基础。我们课程就是从最基础的素描、PS开始教,每一步都有老师手把手辅导。而且如果一期学不会,可以免费跟下一期重修,直到学会为止。', + '我完全理解您的担心!其实90%来报名的同学都是零基础。我们课程就是从最基础的素描、PS开始教,每一步都有老师手把手辅导。而且如果一期学不会,可以免费跟下一期重修,直到学会为止。', + 1, 'ACTIVE', 10, + '["STAGE-06"]', '["INT-OBJ-01"]', '["零基础"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["EMPATHETIC"]', '["异议","学不会"]', + 356, 298, 0.8371, 0, 'admin', 'MANUAL'), + +('UTT-006-002', 'corp001', '担心就业-数据支撑', + '您看,这是我们上一届学员的就业数据:就业率96.5%,平均薪资{{平均薪资}}K,最高有拿到18K的。而且我们跟企业是直签合作协议,不是那种随便推荐的。我可以发几个学员的就业Offer截图给您看看。', + '您看,这是我们上一届学员的就业数据:就业率96.5%,平均薪资9.5K,最高有拿到18K的。而且我们跟企业是直签合作协议,不是那种随便推荐的。我可以发几个学员的就业Offer截图给您看看。', + 1, 'ACTIVE', 10, + '["STAGE-06"]', '["INT-OBJ-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["PROUD"]', '["异议","就业"]', + 312, 265, 0.8494, 0, 'admin', 'MANUAL'), + +('UTT-006-003', 'corp001', '觉得太贵-投资回报', + '说实话,这个学费确实不便宜。但您换个角度想:学8个月,入行起薪8K-10K,一年回本还有赚。如果不学,继续在原来的岗位,可能两三年工资都不会涨多少。这笔账怎么算都是学习更划算。', + '说实话,这个学费确实不便宜。但您换个角度想:学8个月,入行起薪8K-10K,一年回本还有赚。如果不学,继续在原来的岗位,可能两三年工资都不会涨多少。这笔账怎么算都是学习更划算。', + 1, 'ACTIVE', 9, + '["STAGE-06"]', '["INT-OBJ-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["EMPATHETIC"]', '["异议","价格"]', + 278, 232, 0.8345, 0, 'admin', 'MANUAL'), + +('UTT-006-004', 'corp001', '想再比较-差异化优势', + '货比三家是对的!不过我建议您比较的时候重点看三点:一是讲师有没有真实项目经验,二是课程是不是项目实战为主,三是就业率有没有真实数据支撑。这三点咱们第九联盟都是实打实的,您去试听一节课就知道差距了。', + '货比三家是对的!不过我建议您比较的时候重点看三点:一是讲师有没有真实项目经验,二是课程是不是项目实战为主,三是就业率有没有真实数据支撑。这三点咱们第九联盟都是实打实的,您去试听一节课就知道差距了。', + 1, 'ACTIVE', 8, + '["STAGE-06"]', '["INT-OBJ-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["PROUD"]', '["异议","比较"]', + 198, 165, 0.8333, 0, 'admin', 'MANUAL'), + +('UTT-006-005', 'corp001', '时间不允许-灵活方案', + '如果时间不允许全日制,我们还有周末班和线上直播班。周末班每周六日上课,8个月也能学完;线上班更灵活,录播+直播结合,下班在家就能学。而且线上班的学费比全日制便宜不少。', + '如果时间不允许全日制,我们还有周末班和线上直播班。周末班每周六日上课,8个月也能学完;线上班更灵活,录播+直播结合,下班在家就能学。而且线上班的学费比全日制便宜不少。', + 1, 'ACTIVE', 8, + '["STAGE-06"]', '["INT-OBJ-01"]', '["在职人员"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["FRIENDLY"]', '["异议","时间"]', + 178, 148, 0.8315, 0, 'admin', 'MANUAL'), + +('UTT-007-001', 'corp001', '限时优惠-制造紧迫感', + '{{学员姓名}},这个优惠真的到本周五就结束了,过了就恢复原价。而且咱们这一期的名额只剩3个了,您要是确定报名,我现在就帮您锁定名额和优惠,先占个位置,后面再补资料都行。', + '这个优惠真的到本周五就结束了,过了就恢复原价。而且咱们这一期的名额只剩3个了,您要是确定报名,我现在就帮您锁定名额和优惠,先占个位置,后面再补资料都行。', + 1, 'ACTIVE', 9, + '["STAGE-07"]', '["INT-CLOSE-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["URGENT"]', '["促成","限时"]', + 245, 208, 0.8490, 0, 'admin', 'MANUAL'), + +('UTT-007-002', 'corp001', '试听邀约-降低门槛', + '要不这样,您先交{{试听押金}}元押金来试听一周,如果听完了觉得不适合,全额退款;如果觉得好,押金直接抵扣学费。这样您也没有任何风险,可以先感受一下咱们的教学水平。', + '要不这样,您先交500元押金来试听一周,如果听完了觉得不适合,全额退款;如果觉得好,押金直接抵扣学费。这样您也没有任何风险,可以先感受一下咱们的教学水平。', + 1, 'ACTIVE', 9, + '["STAGE-07"]', '["INT-CLOSE-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["FRIENDLY"]', '["促成","试听"]', + 278, 235, 0.8453, 0, 'admin', 'MANUAL'), + +('UTT-007-003', 'corp001', '名额紧张', + '这一期「游戏美术全能班」限定25人,现在已经报了22个了。小班制是我们坚持的教学标准,人多了老师照顾不过来。您要是决定报名,我建议今天就定下来,不然名额满了就要等下一期,至少推迟两个月。', + '这一期「游戏美术全能班」限定25人,现在已经报了22个了。小班制是我们坚持的教学标准,人多了老师照顾不过来。您要是决定报名,我建议今天就定下来,不然名额满了就要等下一期,至少推迟两个月。', + 1, 'ACTIVE', 8, + '["STAGE-07"]', '["INT-CLOSE-01"]', '["通用"]', '["GAME_ART","3D_MODEL"]', '["URGENT"]', '["促成","名额"]', + 198, 168, 0.8485, 0, 'admin', 'MANUAL'), + +('UTT-007-004', 'corp001', '成功案例-学员故事', + '跟您分享一个真实案例:去年有个学员跟您情况很像,也是零基础转行,学了8个月后进了{{合作企业}},起薪11K。上个月他还给我发消息说已经涨到15K了。他的起点跟您一样,只要肯学,您也可以做到。', + '跟您分享一个真实案例:去年有个学员跟您情况很像,也是零基础转行,学了8个月后进了米哈游,起薪11K。上个月他还给我发消息说已经涨到15K了。他的起点跟您一样,只要肯学,您也可以做到。', + 1, 'ACTIVE', 9, + '["STAGE-07"]', '["INT-CLOSE-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["EXCITED"]', '["促成","案例"]', + 312, 268, 0.8589, 0, 'admin', 'MANUAL'), + +('UTT-008-001', 'corp001', '未报名跟进', + '{{学员姓名}}您好!上次聊完之后,您说需要考虑几天。不知道现在考虑得怎么样了?有什么新的顾虑或者问题,都可以随时跟我沟通,我帮您一起解决。', + '您好!上次聊完之后,您说需要考虑几天。不知道现在考虑得怎么样了?有什么新的顾虑或者问题,都可以随时跟我沟通,我帮您一起解决。', + 1, 'ACTIVE', 7, + '["STAGE-08"]', '["INT-FOLLOW-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["FRIENDLY"]', '["跟进","未报名"]', + 156, 128, 0.8205, 0, 'admin', 'MANUAL'), + +('UTT-008-002', 'corp001', '已报名课前关怀', + '恭喜{{学员姓名}}成为第九联盟的一员!开课之前,我建议您先熟悉一下PS的基础操作,我们给您准备了预习视频。另外记得提前准备好笔记本电脑,配置要求我发您微信上了。期待在课堂上见到您!', + '恭喜成为第九联盟的一员!开课之前,我建议您先熟悉一下PS的基础操作,我们给您准备了预习视频。另外记得提前准备好笔记本电脑,配置要求我发您微信上了。期待在课堂上见到您!', + 1, 'ACTIVE', 8, + '["STAGE-08"]', '["INT-FOLLOW-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["EXCITED"]', '["跟进","课前"]', + 178, 148, 0.8315, 0, 'admin', 'MANUAL'), + +('UTT-008-003', 'corp001', '学习过程关怀', + '{{学员姓名}},最近学习进度怎么样?项目作业有没有遇到什么困难?如果哪个知识点没搞懂,一定要主动问老师,也可以直接找我。咱们既然报名了,就要一鼓作气学出来,不要中途掉队!', + '最近学习进度怎么样?项目作业有没有遇到什么困难?如果哪个知识点没搞懂,一定要主动问老师,也可以直接找我。咱们既然报名了,就要一鼓作气学出来,不要中途掉队!', + 1, 'ACTIVE', 7, + '["STAGE-08"]', '["INT-FOLLOW-01"]', '["通用"]', '["GAME_ART","3D_MODEL","CONCEPT_ART","VFX"]', '["FRIENDLY"]', '["跟进","学习过程"]', + 134, 112, 0.8358, 0, 'admin', 'MANUAL'); diff --git a/docker-compose.yml b/docker-compose.yml index f18b52f..2a392df 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,24 +1,14 @@ version: '3.8' services: - nginx: - image: nginx:alpine - container_name: ai-assistant-nginx - restart: always - ports: - - "80:80" - volumes: - - ./nginx.conf:/etc/nginx/conf.d/default.conf - depends_on: - - gateway - + # ---------------------- 基础设施 ---------------------- mysql: image: mysql:5.7 container_name: ai-assistant-mysql restart: always environment: - MYSQL_ROOT_PASSWORD: root - MYSQL_DATABASE: ai_assistant + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root} + MYSQL_DATABASE: ${MYSQL_DATABASE:-ai_assistant} MYSQL_CHARACTER_SET_SERVER: utf8mb4 MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci ports: @@ -28,10 +18,13 @@ services: - ./init.sql:/docker-entrypoint-initdb.d/init.sql command: --default-authentication-plugin=mysql_native_password healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-proot"] + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD:-root}"] interval: 10s timeout: 5s retries: 5 + start_period: 30s + networks: + - ai-assistant-network redis: image: redis:6-alpine @@ -46,6 +39,8 @@ services: interval: 10s timeout: 5s retries: 5 + networks: + - ai-assistant-network rabbitmq: image: rabbitmq:3-management-alpine @@ -55,8 +50,8 @@ services: - "5672:5672" - "15672:15672" environment: - RABBITMQ_DEFAULT_USER: admin - RABBITMQ_DEFAULT_PASS: admin + RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER:-admin} + RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS:-admin} volumes: - rabbitmq_data:/var/lib/rabbitmq healthcheck: @@ -64,15 +59,16 @@ services: interval: 10s timeout: 5s retries: 5 + networks: + - ai-assistant-network + # ---------------------- 后端服务 ---------------------- gateway: build: ./backend/gateway container_name: ai-assistant-gateway restart: always ports: - "8080:8080" - environment: - - SPRING_PROFILES_ACTIVE=docker depends_on: - auth-service - archive-service @@ -82,6 +78,8 @@ services: - generation-service - kb-admin-service - analytics-service + networks: + - ai-assistant-network auth-service: build: ./backend/auth-service @@ -92,10 +90,12 @@ services: environment: - MYSQL_HOST=mysql - REDIS_HOST=redis - - SPRING_PROFILES_ACTIVE=docker + - JWT_SECRET=${JWT_SECRET:-your-256-bit-secret-key-here-at-least-32-characters} depends_on: - mysql - redis + networks: + - ai-assistant-network archive-service: build: ./backend/archive-service @@ -107,11 +107,19 @@ services: - MYSQL_HOST=mysql - REDIS_HOST=redis - RABBITMQ_HOST=rabbitmq - - SPRING_PROFILES_ACTIVE=docker + - WECOM_ARCHIVE_SECRET=${WECOM_ARCHIVE_SECRET:-} + - WECOM_ARCHIVE_RSA_KEY=${WECOM_ARCHIVE_RSA_KEY:-} + - WECOM_ARCHIVE_CALLBACK_TOKEN=${WECOM_ARCHIVE_CALLBACK_TOKEN:-} + - WECOM_ARCHIVE_SDK_PATH=${WECOM_ARCHIVE_SDK_PATH:-/app/sdk} + # 如需真实企微SDK,取消注释下行并将Linux .so文件放入 ./sdk/linux/ + # volumes: + # - ./sdk/linux:/app/sdk depends_on: - mysql - redis - rabbitmq + networks: + - ai-assistant-network conversation-service: build: ./backend/conversation-service @@ -123,11 +131,12 @@ services: - MYSQL_HOST=mysql - REDIS_HOST=redis - RABBITMQ_HOST=rabbitmq - - SPRING_PROFILES_ACTIVE=docker depends_on: - mysql - redis - rabbitmq + networks: + - ai-assistant-network intent-service: build: ./backend/intent-service @@ -139,11 +148,12 @@ services: - MYSQL_HOST=mysql - REDIS_HOST=redis - RABBITMQ_HOST=rabbitmq - - SPRING_PROFILES_ACTIVE=docker depends_on: - mysql - redis - rabbitmq + networks: + - ai-assistant-network recommend-service: build: ./backend/recommendation-service @@ -155,11 +165,13 @@ services: - MYSQL_HOST=mysql - REDIS_HOST=redis - RABBITMQ_HOST=rabbitmq - - SPRING_PROFILES_ACTIVE=docker + - DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY:-} depends_on: - mysql - redis - rabbitmq + networks: + - ai-assistant-network generation-service: build: ./backend/generation-service @@ -169,9 +181,11 @@ services: - "8086:8086" environment: - REDIS_HOST=redis - - SPRING_PROFILES_ACTIVE=docker + - DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY:-} depends_on: - redis + networks: + - ai-assistant-network kb-admin-service: build: ./backend/kb-admin-service @@ -182,10 +196,11 @@ services: environment: - MYSQL_HOST=mysql - REDIS_HOST=redis - - SPRING_PROFILES_ACTIVE=docker depends_on: - mysql - redis + networks: + - ai-assistant-network analytics-service: build: ./backend/analytics-service @@ -196,12 +211,58 @@ services: environment: - MYSQL_HOST=mysql - REDIS_HOST=redis - - SPRING_PROFILES_ACTIVE=docker depends_on: - mysql - redis + networks: + - ai-assistant-network + # ---------------------- 前端服务 ---------------------- + admin: + build: ./frontend/admin + container_name: ai-assistant-admin + restart: always + ports: + - "5174:80" + depends_on: + - gateway + networks: + - ai-assistant-network + + sidebar: + build: ./frontend/sidebar + container_name: ai-assistant-sidebar + restart: always + ports: + - "5173:80" + depends_on: + - gateway + networks: + - ai-assistant-network + + # ---------------------- 统一入口 Nginx ---------------------- + nginx: + image: nginx:alpine + container_name: ai-assistant-nginx + restart: always + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/conf.d/default.conf + depends_on: + - gateway + - admin + - sidebar + networks: + - ai-assistant-network + +# ---------------------- 数据卷 ---------------------- volumes: mysql_data: redis_data: rabbitmq_data: + +# ---------------------- 网络 ---------------------- +networks: + ai-assistant-network: + driver: bridge diff --git a/docs/deploy/DEPLOY.md b/docs/deploy/DEPLOY.md new file mode 100644 index 0000000..b157193 --- /dev/null +++ b/docs/deploy/DEPLOY.md @@ -0,0 +1,157 @@ +# 第九联盟AI坐席辅助系统 - Docker 部署指南 + +## 前置条件 + +- Docker Engine >= 20.10 +- Docker Compose >= 2.0 +- 服务器内存 >= 4GB(推荐 8GB) +- 磁盘空间 >= 10GB + +## 快速开始 + +### 1. 克隆代码 + +```bash +git clone +cd agent_9art +``` + +### 2. 配置环境变量 + +```bash +# 复制环境变量模板 +cp .env .env.local + +# 编辑 .env.local,填入真实配置 +vim .env.local +``` + +**必须填写的配置项:** + +| 变量 | 说明 | 获取方式 | +|------|------|---------| +| `WECOM_ARCHIVE_SECRET` | 企微会话存档 Secret | 企业微信管理后台 → 管理工具 → 会话内容存档 | +| `WECOM_ARCHIVE_RSA_KEY` | RSA 私钥(PKCS#1)| 自行生成,公钥配置到企微后台 | +| `WECOM_ARCHIVE_CALLBACK_TOKEN` | 回调验证 Token | 企微后台配置接收消息时设置 | +| `DASHSCOPE_API_KEY` | 通义千问 API Key | 阿里云 DashScope 控制台 | + +**可选修改:** +- `MYSQL_ROOT_PASSWORD`:数据库 root 密码(默认 root) +- `RABBITMQ_DEFAULT_PASS`:RabbitMQ 密码(默认 admin) + +### 3. 构建并启动 + +```bash +# 一键构建并启动全部服务 +docker-compose up -d --build + +# 首次启动需要等待 MySQL 初始化(约 30-60 秒) +docker-compose logs -f mysql +``` + +### 4. 验证状态 + +```bash +# 查看所有容器状态 +docker-compose ps + +# 预期输出:所有容器状态为 Up (healthy) +# NAME STATUS +# ai-assistant-mysql Up (healthy) +# ai-assistant-redis Up (healthy) +# ai-assistant-rabbitmq Up (healthy) +# ai-assistant-gateway Up +# ai-assistant-auth Up +# ... +``` + +### 5. 访问系统 + +| 地址 | 说明 | +|------|------| +| http://localhost/admin | 管理后台(Admin Dashboard) | +| http://localhost/sidebar | 侧边栏 H5(企微侧边栏) | +| http://localhost/api/v1/... | API 接口(通过 Gateway) | +| http://localhost:15672 | RabbitMQ 管理面板(guest/admin) | + +## 企微 SDK Linux 部署(archive-service) + +Windows 开发环境使用 `.dll`,Linux 服务器需要 `.so` 文件: + +```bash +# 1. 从企微官网下载 Linux SDK,解压得到 libWeWorkFinanceSdk_Java.so +# 2. 放置到项目目录 +mkdir -p ./sdk/linux +cp /path/to/libWeWorkFinanceSdk_Java.so ./sdk/linux/ + +# 3. 修改 docker-compose.yml,取消注释 archive-service 的 volumes 挂载 +# volumes: +# - ./sdk/linux:/app/sdk + +# 4. 重新启动 archive-service +docker-compose up -d --build archive-service +``` + +## 常用命令 + +```bash +# 查看日志 +docker-compose logs -f # 全部服务 +docker-compose logs -f gateway # 指定服务 + +# 重启服务 +docker-compose restart gateway + +# 停止全部服务 +docker-compose down + +# 停止并删除数据卷(慎用,会清空数据库) +docker-compose down -v + +# 重新构建并启动单个服务 +docker-compose up -d --build auth-service + +# 进入容器调试 +docker exec -it ai-assistant-mysql mysql -uroot -p +docker exec -it ai-assistant-redis redis-cli +``` + +## 服务端口对照 + +| 服务 | 容器端口 | 宿主机端口 | 说明 | +|------|---------|-----------|------| +| Nginx | 80 | 80 | 统一入口 | +| Gateway | 8080 | 8080 | API 网关 | +| Auth | 8081 | 8081 | 认证服务 | +| Archive | 8082 | 8082 | 会话存档 | +| Conversation | 8083 | 8083 | 对话管理 | +| Intent | 8084 | 8084 | 意图识别 | +| Recommendation | 8085 | 8085 | 话术推荐 | +| Generation | 8086 | 8086 | AI 生成 | +| KB-Admin | 8087 | 8087 | 管理后台 API | +| Analytics | 8088 | 8088 | 数据分析 | +| Admin | 80 | 5174 | 前端管理后台 | +| Sidebar | 80 | 5173 | 前端侧边栏 | +| MySQL | 3306 | 3306 | 数据库 | +| Redis | 6379 | 6379 | 缓存 | +| RabbitMQ | 5672 / 15672 | 5672 / 15672 | 消息队列 / 管理面板 | + +## 数据持久化 + +Docker Compose 使用命名卷持久化数据: + +| 卷名 | 说明 | +|------|------| +| `mysql_data` | MySQL 数据文件 | +| `redis_data` | Redis 持久化数据 | +| `rabbitmq_data` | RabbitMQ 消息队列数据 | + +数据保存在 Docker 管理的卷中,执行 `docker-compose down` 不会删除数据。如需彻底清空,使用 `docker-compose down -v`。 + +## 生产环境建议 + +1. **修改默认密码**:`.env` 中的 `MYSQL_ROOT_PASSWORD` 和 `RABBITMQ_DEFAULT_PASS` 不要使用默认值 +2. **使用 HTTPS**:Nginx 配置 SSL 证书,将 80 端口重定向到 443 +3. **限制端口暴露**:生产环境只暴露 Nginx 80/443 端口,其他后端服务端口不映射到宿主机 +4. **日志管理**:配置 Docker 日志轮转,避免磁盘占满 +5. **监控告警**:集成 Prometheus + Grafana 监控各服务健康状态 diff --git a/fix-admin-password.sh b/fix-admin-password.sh new file mode 100644 index 0000000..c23bf50 --- /dev/null +++ b/fix-admin-password.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# 在服务器上执行,重置 admin 密码为 admin123 + +cd /www/wwwroot/deploy-package + +# 生成新的 BCrypt 哈希(strength=10) +# 使用 Java 生成 +NEW_HASH=$(docker run --rm eclipse-temurin:17-jre-alpine sh -c ' + wget -q https://repo1.maven.org/maven2/org/springframework/security/spring-security-crypto/5.8.0/spring-security-crypto-5.8.0.jar -O /tmp/crypto.jar 2>/dev/null || true + java -cp "/tmp/crypto.jar" -version 2>&1 | head -1 +') + +echo "直接在 MySQL 中更新密码哈希..." + +# 这个哈希是通过 BCryptPasswordEncoder 对 "admin123" 加密得到的(strength=10) +# 你可以用在线工具验证:https://bcrypt-generator.com/ +HASH='$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iOE1Qh6G' + +docker exec -i ai-assistant-mysql mysql -uroot -proot -e " +UPDATE ai_assistant.admins SET password = '${HASH}' WHERE username = 'admin'; +SELECT username, password FROM ai_assistant.admins WHERE username = 'admin'; +" 2>&1 + +echo "" +echo "测试登录..." +curl -s -X POST http://localhost:8087/api/v1/admin/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"admin123"}' diff --git a/frontend/admin/.gitignore b/frontend/admin/.gitignore new file mode 100644 index 0000000..2c4cd1a --- /dev/null +++ b/frontend/admin/.gitignore @@ -0,0 +1,25 @@ +# 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? +node_modules/ \ No newline at end of file diff --git a/frontend/admin/Dockerfile b/frontend/admin/Dockerfile index eb95269..bd8ce76 100644 --- a/frontend/admin/Dockerfile +++ b/frontend/admin/Dockerfile @@ -6,6 +6,6 @@ COPY . . RUN npm run build FROM nginx:alpine -COPY --from=builder /app/dist /usr/share/nginx/html/admin +COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 diff --git a/frontend/admin/debug-dash.cjs b/frontend/admin/debug-dash.cjs new file mode 100644 index 0000000..1ff2d7a --- /dev/null +++ b/frontend/admin/debug-dash.cjs @@ -0,0 +1,19 @@ +const puppeteer = require('puppeteer-core'); +(async () => { + const browser = await puppeteer.launch({ + executablePath: 'C:/Program Files/Google/Chrome/Application/chrome.exe', + headless: true, + args: ['--no-sandbox', '--disable-setuid-sandbox'] + }); + const page = await browser.newPage(); + // 先登录 + await page.goto('http://localhost:5174/login'); + await new Promise(r => setTimeout(r, 1000)); + await page.type('input[type="text"]', 'admin'); + await page.type('input[type="password"]', 'admin123'); + await page.click('button[type="submit"]'); + await new Promise(r => setTimeout(r, 3000)); + // 已经在首页(Dashboard) + await page.screenshot({ path: 'D:/www/agent_9art/admin-dash.png', fullPage: true }); + await browser.close(); +})(); diff --git a/frontend/admin/nginx.conf b/frontend/admin/nginx.conf new file mode 100644 index 0000000..3599b59 --- /dev/null +++ b/frontend/admin/nginx.conf @@ -0,0 +1,25 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # API 代理到 Gateway + location /api/ { + proxy_pass http://gateway:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # 静态资源 + location / { + try_files $uri $uri/ /index.html; + } + + # 缓存静态资源 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1d; + add_header Cache-Control "public, immutable"; + } +} diff --git a/frontend/admin/package-lock.json b/frontend/admin/package-lock.json new file mode 100644 index 0000000..e47945a --- /dev/null +++ b/frontend/admin/package-lock.json @@ -0,0 +1,4282 @@ +{ + "name": "ai-assistant-admin", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ai-assistant-admin", + "version": "1.0.0", + "dependencies": { + "@ant-design/charts": "^2.0.0", + "antd": "^5.12.0", + "axios": "^1.6.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.20.0" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.0.0", + "typescript": "^5.0.0", + "vite": "^5.0.0" + } + }, + "node_modules/@ant-design/charts": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/@ant-design/charts/-/charts-2.6.7.tgz", + "integrity": "sha512-XfmsnspUpfrMlRFGTwmHJ2TPKcosq5a5nSxAfIOpEXAvmJBT2N16oejGTZhUFTzba8W3XtBOziwRAXmDmLUqvA==", + "license": "MIT", + "dependencies": { + "@ant-design/graphs": "^2.1.1", + "@ant-design/plots": "^2.6.7", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": ">=16.8.4", + "react-dom": ">=16.8.4" + } + }, + "node_modules/@ant-design/charts-util": { + "version": "0.0.1-alpha.7", + "resolved": "https://registry.npmjs.org/@ant-design/charts-util/-/charts-util-0.0.1-alpha.7.tgz", + "integrity": "sha512-Yh0o6EdO6SvdSnStFZMbnUzjyymkVzV+TQ9ymVW9hlVgO/fUkUII3JYSdV+UVcFnYwUF0YiDKuSTLCZNAzg2bQ==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": ">=16.8.4", + "react-dom": ">=16.8.4" + } + }, + "node_modules/@ant-design/colors": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz", + "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.24.0.tgz", + "integrity": "sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "classnames": "^2.3.1", + "csstype": "^3.1.3", + "rc-util": "^5.35.0", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz", + "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/graphs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@ant-design/graphs/-/graphs-2.1.1.tgz", + "integrity": "sha512-qT3Oo8BWeoAmZEy9gfR6uIk+rczbNJ3sWXKonoOD5koATWv7dY0kgvS1JnhdM1QW4FkfPPJTeQVSlRRUtvWDwA==", + "license": "MIT", + "dependencies": { + "@ant-design/charts-util": "0.0.1-alpha.7", + "@antv/g6": "^5.0.44", + "@antv/g6-extension-react": "^0.2.0", + "@antv/graphin": "^3.0.4", + "lodash": "^4.17.21", + "styled-components": "^6.1.15" + }, + "peerDependencies": { + "react": ">=16.8.4", + "react-dom": ">=16.8.4" + } + }, + "node_modules/@ant-design/graphs/node_modules/styled-components": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.4.1.tgz", + "integrity": "sha512-ADu2dF53esUzzM4I0ewxhxFtsDd6v4V6dNkg3vG0iFKhnt06sJneTZnRvujAosZwW0XD58IKgGMQoqri4wHRqg==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.4.0", + "css-to-react-native": "3.2.0", + "csstype": "3.2.3", + "stylis": "4.3.6" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "css-to-react-native": ">= 3.2.0", + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-native": ">= 0.68.0" + }, + "peerDependenciesMeta": { + "css-to-react-native": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@ant-design/graphs/node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/plots": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/@ant-design/plots/-/plots-2.6.8.tgz", + "integrity": "sha512-QsunUs2d5rbq/1BwVhga/siA5H50OaG23YopMYwPD4sPsza6NQzPQ8FM3elNIsD/BIk298tihqX1cJ/MmvVJbQ==", + "license": "MIT", + "dependencies": { + "@ant-design/charts-util": "0.0.3", + "@antv/event-emitter": "^0.1.3", + "@antv/g": "^6.1.7", + "@antv/g2": "^5.2.7", + "@antv/g2-extension-plot": "^0.2.1", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": ">=16.8.4", + "react-dom": ">=16.8.4" + } + }, + "node_modules/@ant-design/plots/node_modules/@ant-design/charts-util": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@ant-design/charts-util/-/charts-util-0.0.3.tgz", + "integrity": "sha512-x1H7UT6t4dXAyGRoHqlOnEsEqBSTANFGTZEAMI0CWYhYUpp13n0o9grl9oPtoL6FEQMjUBTY+zGJKlHkz8smMw==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": ">=16.8.4", + "react-dom": ">=16.8.4" + } + }, + "node_modules/@ant-design/react-slick": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "resize-observer-polyfill": "^1.5.1", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@antv/algorithm": { + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/@antv/algorithm/-/algorithm-0.1.26.tgz", + "integrity": "sha512-DVhcFSQ8YQnMNW34Mk8BSsfc61iC1sAnmcfYoXTAshYHuU50p/6b7x3QYaGctDNKWGvi1ub7mPcSY0bK+aN0qg==", + "license": "MIT", + "dependencies": { + "@antv/util": "^2.0.13", + "tslib": "^2.0.0" + } + }, + "node_modules/@antv/algorithm/node_modules/@antv/util": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@antv/util/-/util-2.0.17.tgz", + "integrity": "sha512-o6I9hi5CIUvLGDhth0RxNSFDRwXeywmt6ExR4+RmVAzIi48ps6HUy+svxOCayvrPBN37uE6TAc2KDofRo0nK9Q==", + "license": "ISC", + "dependencies": { + "csstype": "^3.0.8", + "tslib": "^2.0.3" + } + }, + "node_modules/@antv/component": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/@antv/component/-/component-2.1.11.tgz", + "integrity": "sha512-dTdz8VAd3rpjOaGEZTluz82mtzrP4XCtNlNQyrxY7VNRNcjtvpTLDn57bUL2lRu1T+iklKvgbE2llMriWkq9vQ==", + "license": "MIT", + "dependencies": { + "@antv/g": "^6.1.11", + "@antv/scale": "^0.4.16", + "@antv/util": "^3.3.10", + "svg-path-parser": "^1.1.0" + } + }, + "node_modules/@antv/component/node_modules/@antv/scale": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@antv/scale/-/scale-0.4.16.tgz", + "integrity": "sha512-5wg/zB5kXHxpTV5OYwJD3ja6R8yTiqIOkjOhmpEJiowkzRlbEC/BOyMvNUq5fqFIHnMCE9woO7+c3zxEQCKPjw==", + "license": "MIT", + "dependencies": { + "@antv/util": "^3.3.7", + "color-string": "^1.5.5", + "fecha": "^4.2.1" + } + }, + "node_modules/@antv/coord": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@antv/coord/-/coord-0.4.7.tgz", + "integrity": "sha512-UTbrMLhwJUkKzqJx5KFnSRpU3BqrdLORJbwUbHK2zHSCT3q3bjcFA//ZYLVfIlwqFDXp/hzfMyRtp0c77A9ZVA==", + "license": "MIT", + "dependencies": { + "@antv/scale": "^0.4.12", + "@antv/util": "^2.0.13", + "gl-matrix": "^3.4.3" + } + }, + "node_modules/@antv/coord/node_modules/@antv/scale": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@antv/scale/-/scale-0.4.16.tgz", + "integrity": "sha512-5wg/zB5kXHxpTV5OYwJD3ja6R8yTiqIOkjOhmpEJiowkzRlbEC/BOyMvNUq5fqFIHnMCE9woO7+c3zxEQCKPjw==", + "license": "MIT", + "dependencies": { + "@antv/util": "^3.3.7", + "color-string": "^1.5.5", + "fecha": "^4.2.1" + } + }, + "node_modules/@antv/coord/node_modules/@antv/scale/node_modules/@antv/util": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/@antv/util/-/util-3.3.11.tgz", + "integrity": "sha512-FII08DFM4ABh2q5rPYdr0hMtKXRgeZazvXaFYCs7J7uTcWDHUhczab2qOCJLNDugoj8jFag1djb7wS9ehaRYBg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "gl-matrix": "^3.3.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@antv/coord/node_modules/@antv/util": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@antv/util/-/util-2.0.17.tgz", + "integrity": "sha512-o6I9hi5CIUvLGDhth0RxNSFDRwXeywmt6ExR4+RmVAzIi48ps6HUy+svxOCayvrPBN37uE6TAc2KDofRo0nK9Q==", + "license": "ISC", + "dependencies": { + "csstype": "^3.0.8", + "tslib": "^2.0.3" + } + }, + "node_modules/@antv/event-emitter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@antv/event-emitter/-/event-emitter-0.1.3.tgz", + "integrity": "sha512-4ddpsiHN9Pd4UIlWuKVK1C4IiZIdbwQvy9i7DUSI3xNJ89FPUFt8lxDYj8GzzfdllV0NkJTRxnG+FvLk0llidg==", + "license": "MIT" + }, + "node_modules/@antv/expr": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@antv/expr/-/expr-1.0.2.tgz", + "integrity": "sha512-vrfdmPHkTuiS5voVutKl2l06w1ihBh9A8SFdQPEE+2KMVpkymzGOF1eWpfkbGZ7tiFE15GodVdhhHomD/hdIwg==", + "license": "MIT" + }, + "node_modules/@antv/g": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@antv/g/-/g-6.3.1.tgz", + "integrity": "sha512-WYEKqy86LHB2PzTmrZXrIsIe+3Epeds2f68zceQ+BJtRoGki7Sy4IhlC8LrUMztgfT1t3d/0L745NWZwITroKA==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.7.0", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "html2canvas": "^1.4.1" + } + }, + "node_modules/@antv/g-canvas": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@antv/g-canvas/-/g-canvas-2.2.0.tgz", + "integrity": "sha512-h7zVBBo2aO64DuGKvq9sG+yTU3sCUb9DALCVm7nz8qGPs8hhLuFOkKPEzUDNfNYZGJUGzY8UDtJ3QRGRFcvEQg==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.7.0", + "@antv/g-math": "3.1.0", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-lite": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@antv/g-lite/-/g-lite-2.7.0.tgz", + "integrity": "sha512-uSzgHYa5bwR5L2Au7/5tsOhFmXKZKLPBH90+Q9bP9teVs5VT4kOAi0isPSpDI8uhdDC2/VrfTWu5K9HhWI6FWw==", + "license": "MIT", + "dependencies": { + "@antv/g-math": "3.1.0", + "@antv/util": "^3.3.5", + "@antv/vendor": "^1.0.3", + "@babel/runtime": "^7.25.6", + "eventemitter3": "^5.0.1", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@antv/g-math/-/g-math-3.1.0.tgz", + "integrity": "sha512-DtN1Gj/yI0UiK18nSBsZX8RK0LszGwqfb+cBYWgE+ddyTm8dZnW4tPUhV7QXePsS6/A5hHC+JFpAAK7OEGo5ZQ==", + "license": "MIT", + "dependencies": { + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-dragndrop": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@antv/g-plugin-dragndrop/-/g-plugin-dragndrop-2.1.1.tgz", + "integrity": "sha512-+aesDUJVQDs6UJ2bOBbDlaGAPCfHmU0MbrMTlQlfpwNplWueqtgVAZ3L57oZ2ZGHRWUHiRwZGPjXMBM3O2LELw==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.7.0", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-svg": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@antv/g-svg/-/g-svg-2.1.1.tgz", + "integrity": "sha512-gVzBkjqA8FzDTbkuIxj6L0Omz/X/hFbYLzK6alWr0sHTfywqP6czcjDUJU8DF2MRIY1Twy55uZYW4dqqLXOXXg==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.7.0", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g2": { + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/@antv/g2/-/g2-5.4.8.tgz", + "integrity": "sha512-IvgIpwmT4M5/QAd3Mn2WiHIDeBqFJ4WA2gcZhRRSZuZ2KmgCqZWZwwIT0hc+kIGxwYeDoCQqf//t6FMVu3ryBg==", + "license": "MIT", + "dependencies": { + "@antv/component": "^2.1.9", + "@antv/coord": "^0.4.7", + "@antv/event-emitter": "^0.1.3", + "@antv/expr": "^1.0.2", + "@antv/g": "^6.1.24", + "@antv/g-canvas": "^2.0.43", + "@antv/g-plugin-dragndrop": "^2.0.35", + "@antv/scale": "^0.5.1", + "@antv/util": "^3.3.10", + "@antv/vendor": "^1.0.11", + "flru": "^1.0.2", + "pdfast": "^0.2.0" + } + }, + "node_modules/@antv/g2-extension-plot": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@antv/g2-extension-plot/-/g2-extension-plot-0.2.2.tgz", + "integrity": "sha512-KJXCXO7as+h0hDqirGXf1omrNuYzQmY3VmBmp7lIvkepbQ7sz3pPwy895r1FWETGF3vTk5UeFcAF5yzzBHWgbw==", + "dependencies": { + "@antv/g2": "^5.1.8", + "@antv/util": "^3.3.5", + "@antv/vendor": "^1.0.10" + } + }, + "node_modules/@antv/g6": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@antv/g6/-/g6-5.1.1.tgz", + "integrity": "sha512-50bXxMUf4mChyOv4ePVeWZLwotih9VunKfp0a++Wofv/wCyY8fb9+CV2wouIBCOZnd5ydBRA4NNaX9yLJzqa2w==", + "license": "MIT", + "dependencies": { + "@antv/algorithm": "^0.1.26", + "@antv/component": "^2.1.7", + "@antv/event-emitter": "^0.1.3", + "@antv/g": "^6.1.28", + "@antv/g-canvas": "^2.0.48", + "@antv/g-plugin-dragndrop": "^2.0.38", + "@antv/graphlib": "^2.0.4", + "@antv/hierarchy": "^0.7.1", + "@antv/layout": "^2.0.0", + "@antv/util": "^3.3.11", + "bubblesets-js": "^2.3.4" + } + }, + "node_modules/@antv/g6-extension-react": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@antv/g6-extension-react/-/g6-extension-react-0.2.7.tgz", + "integrity": "sha512-X/zxGiL/kyJ+5xteX1+P2mI07oLw+zfvKcIHxfynL7IGCQCwQ6q91LkJaOlSDTuWhNRXwnwJ4Cf2Nt/9Dhq5Dg==", + "license": "MIT", + "dependencies": { + "@antv/g": "^6.1.24", + "@antv/g-svg": "^2.0.38" + }, + "peerDependencies": { + "@antv/g6": "^5.1.0", + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@antv/graphin": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@antv/graphin/-/graphin-3.0.5.tgz", + "integrity": "sha512-V/j8R8Ty44wUqxVIYLdpPuIO8WWCTIVq1eBJg5YRunL5t5o5qAFpC/qkQxslbBMWyKdIH0oWBnvHA74riGi7cw==", + "license": "MIT", + "dependencies": { + "@antv/g6": "^5.0.28" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.1.0", + "react-dom": "^18.0.0 || ^19.1.0" + } + }, + "node_modules/@antv/graphlib": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@antv/graphlib/-/graphlib-2.0.4.tgz", + "integrity": "sha512-zc/5oQlsdk42Z0ib1mGklwzhJ5vczLFiPa1v7DgJkTbgJ2YxRh9xdarf86zI49sKVJmgbweRpJs7Nu5bIiwv4w==", + "license": "MIT", + "dependencies": { + "@antv/event-emitter": "^0.1.3" + } + }, + "node_modules/@antv/hierarchy": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@antv/hierarchy/-/hierarchy-0.7.1.tgz", + "integrity": "sha512-7r22r+HxfcRZp79ZjGmsn97zgC1Iajrv0Mm9DIgx3lPfk+Kme2MG/+EKdZj1iEBsN0rJRzjWVPGL5YrBdVHchw==", + "license": "MIT" + }, + "node_modules/@antv/layout": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@antv/layout/-/layout-2.0.0.tgz", + "integrity": "sha512-aCZ3UdNc40SfT7meFV7QTADY2HCnc0DShVw56CJNTI6oExUIVU736grPuL5Dhb8/JrVaU4Y83QPN/P7KafBzlw==", + "license": "MIT", + "dependencies": { + "@antv/event-emitter": "^0.1.3", + "@antv/expr": "^1.0.2", + "@antv/graphlib": "^2.0.0", + "@antv/util": "^3.3.2", + "comlink": "^4.4.1", + "d3-force": "^3.0.0", + "d3-force-3d": "^3.0.5", + "d3-octree": "^1.0.2", + "d3-quadtree": "^3.0.1", + "dagre": "^0.8.5", + "ml-matrix": "^6.10.4", + "tslib": "^2.8.1" + } + }, + "node_modules/@antv/scale": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@antv/scale/-/scale-0.5.2.tgz", + "integrity": "sha512-rTHRAwvpHWC5PGZF/mJ2ZuTDqwwvVBDRph0Uu5PV9BXwzV7K8+9lsqGJ+XHVLxe8c6bKog5nlzvV/dcYb0d5Ow==", + "license": "MIT", + "dependencies": { + "@antv/util": "^3.3.7", + "color-string": "^1.5.5", + "fecha": "^4.2.1" + } + }, + "node_modules/@antv/util": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/@antv/util/-/util-3.3.11.tgz", + "integrity": "sha512-FII08DFM4ABh2q5rPYdr0hMtKXRgeZazvXaFYCs7J7uTcWDHUhczab2qOCJLNDugoj8jFag1djb7wS9ehaRYBg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "gl-matrix": "^3.3.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@antv/vendor": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@antv/vendor/-/vendor-1.0.11.tgz", + "integrity": "sha512-LmhPEQ+aapk3barntaiIxJ5VHno/Tyab2JnfdcPzp5xONh/8VSfed4bo/9xKo5HcUAEydko38vYLfj6lJliLiw==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.2.1", + "@types/d3-color": "^3.1.3", + "@types/d3-dispatch": "^3.0.6", + "@types/d3-dsv": "^3.0.7", + "@types/d3-ease": "^3.0.2", + "@types/d3-fetch": "^3.0.7", + "@types/d3-force": "^3.0.10", + "@types/d3-format": "^3.0.4", + "@types/d3-geo": "^3.1.0", + "@types/d3-hierarchy": "^3.1.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-path": "^3.1.0", + "@types/d3-quadtree": "^3.0.6", + "@types/d3-random": "^3.0.3", + "@types/d3-scale": "^4.0.9", + "@types/d3-scale-chromatic": "^3.1.0", + "@types/d3-shape": "^3.1.7", + "@types/d3-time": "^3.0.4", + "@types/d3-timer": "^3.0.2", + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-dispatch": "^3.0.1", + "d3-dsv": "^3.0.1", + "d3-ease": "^3.0.1", + "d3-fetch": "^3.0.1", + "d3-force": "^3.0.0", + "d3-force-3d": "^3.0.5", + "d3-format": "^3.1.0", + "d3-geo": "^3.1.1", + "d3-geo-projection": "^4.0.0", + "d3-hierarchy": "^3.1.2", + "d3-interpolate": "^3.0.1", + "d3-path": "^3.1.0", + "d3-quadtree": "^3.0.1", + "d3-random": "^3.0.1", + "d3-regression": "^1.3.10", + "d3-scale": "^4.0.2", + "d3-scale-chromatic": "^3.1.0", + "d3-shape": "^3.2.0", + "d3-time": "^3.1.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.0.tgz", + "integrity": "sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6", + "@babel/runtime": "^7.23.6", + "classnames": "^2.2.6", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz", + "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.3.tgz", + "integrity": "sha512-bk/FJ09fLf+NLODMAFll6CfYrHPBioTedhW6lxDBuuWucJEqFUd4l/D/5JgIi3dina6sYahB8iuPAZTNz2pMxw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", + "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.1.tgz", + "integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz", + "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/portal": "^1.0.0-9", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/trigger": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.3.1.tgz", + "integrity": "sha512-ORENF39PeXTzM+gQEshuk460Z8N4+6DkjpxlpE7Q3gYy1iBpLrx0FOJz3h62ryrJZ/3zCAUIkT1Pb/8hHWpb3A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@rc-component/portal": "^1.1.0", + "classnames": "^2.3.2", + "rc-motion": "^2.0.0", + "rc-resize-observer": "^1.3.1", + "rc-util": "^5.44.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/antd": { + "version": "5.29.3", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.29.3.tgz", + "integrity": "sha512-3DdbGCa9tWAJGcCJ6rzR8EJFsv2CtyEbkVabZE14pfgUHfCicWCj0/QzQVLDYg8CPfQk9BH7fHCoTXHTy7MP/A==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.2.1", + "@ant-design/cssinjs": "^1.23.0", + "@ant-design/cssinjs-utils": "^1.1.3", + "@ant-design/fast-color": "^2.0.6", + "@ant-design/icons": "^5.6.1", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.26.0", + "@rc-component/color-picker": "~2.0.1", + "@rc-component/mutate-observer": "^1.1.0", + "@rc-component/qrcode": "~1.1.0", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.3.0", + "classnames": "^2.5.1", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.11", + "rc-cascader": "~3.34.0", + "rc-checkbox": "~3.5.0", + "rc-collapse": "~3.9.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.3.0", + "rc-dropdown": "~4.2.1", + "rc-field-form": "~2.7.1", + "rc-image": "~7.12.0", + "rc-input": "~1.8.0", + "rc-input-number": "~9.5.0", + "rc-mentions": "~2.20.0", + "rc-menu": "~9.16.1", + "rc-motion": "^2.9.5", + "rc-notification": "~5.6.4", + "rc-pagination": "~5.1.0", + "rc-picker": "~4.11.3", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.1", + "rc-resize-observer": "^1.4.3", + "rc-segmented": "~2.7.0", + "rc-select": "~14.16.8", + "rc-slider": "~11.1.9", + "rc-steps": "~6.0.1", + "rc-switch": "~4.1.0", + "rc-table": "~7.54.0", + "rc-tabs": "~15.7.0", + "rc-textarea": "~1.10.2", + "rc-tooltip": "~6.4.0", + "rc-tree": "~5.13.1", + "rc-tree-select": "~5.27.0", + "rc-upload": "~4.11.0", + "rc-util": "^5.44.4", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz", + "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bubblesets-js": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/bubblesets-js/-/bubblesets-js-2.3.4.tgz", + "integrity": "sha512-DyMjHmpkS2+xcFNtyN00apJYL3ESdp9fTrkDr5+9Qg/GPqFmcWgGsK1akZnttE1XFxJ/VMy4DNNGMGYtmFp1Sg==", + "license": "MIT" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comlink": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.2.tgz", + "integrity": "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==", + "license": "Apache-2.0" + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-binarytree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", + "license": "MIT" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force-3d": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", + "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", + "license": "MIT", + "dependencies": { + "d3-binarytree": "1", + "d3-dispatch": "1 - 3", + "d3-octree": "1", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo-projection": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz", + "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==", + "license": "ISC", + "dependencies": { + "commander": "7", + "d3-array": "1 - 3", + "d3-geo": "1.12.0 - 3" + }, + "bin": { + "geo2svg": "bin/geo2svg.js", + "geograticule": "bin/geograticule.js", + "geoproject": "bin/geoproject.js", + "geoquantize": "bin/geoquantize.js", + "geostitch": "bin/geostitch.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-octree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", + "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", + "license": "MIT" + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-regression": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/d3-regression/-/d3-regression-1.3.10.tgz", + "integrity": "sha512-PF8GWEL70cHHWpx2jUQXc68r1pyPHIA+St16muk/XRokETzlegj5LriNKg7o4LR0TySug4nHYPJNNRz/W+/Niw==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "license": "MIT", + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.354", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.354.tgz", + "integrity": "sha512-JaBHwWcfIdmSAfWM5l3uwjGd431j8YEMikZ+K/2nXVuBqJKyZ0f+2h4n4JY5AyNiZmnY9qQr2RU3v9DxDmHMNg==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/flru": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flru/-/flru-1.0.2.tgz", + "integrity": "sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-any-array": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-any-array/-/is-any-array-3.0.0.tgz", + "integrity": "sha512-o4h+tylWykC4BD1vaejp6gDxoM13bwW8FGuNs4yIKpj8xbBJcRxJx8vZpq0dCr7ZDEfeKjmsi/euolKhX6f/ww==", + "license": "MIT" + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ml-array-max": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ml-array-max/-/ml-array-max-2.0.0.tgz", + "integrity": "sha512-QQZ4kENwpWmyNb98UXRDFXrmtIXuXtt1+bSbda/2KA85+F+rrJP8hZk6QOkCQXM2Th9mUDYdq/PNByPdT9ID4A==", + "license": "MIT", + "dependencies": { + "is-any-array": "^3.0.0" + } + }, + "node_modules/ml-array-min": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ml-array-min/-/ml-array-min-2.0.0.tgz", + "integrity": "sha512-GRj6Ky6sW9vGL6yIjgsHmXZ9YgrdmcQ8nCxPqEGeKc6dkfYg1XDYxGFxADUjNuZyoCd5PUscWAS4N+cFaX6hFg==", + "license": "MIT", + "dependencies": { + "is-any-array": "^3.0.0" + } + }, + "node_modules/ml-array-rescale": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ml-array-rescale/-/ml-array-rescale-2.0.0.tgz", + "integrity": "sha512-2GGtKfSno94/kIloWGvpp/U5Q5vLvLrza+SAaGsLeo6Xj4mEbA6Gqx+oTfZFkxnd1grT2X007HfJNs3T5BsiVg==", + "license": "MIT", + "dependencies": { + "is-any-array": "^3.0.0", + "ml-array-max": "^2.0.0", + "ml-array-min": "^2.0.0" + } + }, + "node_modules/ml-matrix": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ml-matrix/-/ml-matrix-6.12.2.tgz", + "integrity": "sha512-GC+BnW+pBh8Auap8goAxY0senAmF0IEoc3HNVSfnfbvGw0buuDIYb9kAKMS1l+GiwJ1rfK2bzJ8IHhwjzATSFA==", + "license": "MIT", + "dependencies": { + "is-any-array": "^3.0.0", + "ml-array-rescale": "^2.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.44", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz", + "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pdfast": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/pdfast/-/pdfast-0.2.0.tgz", + "integrity": "sha512-cq6TTu6qKSFUHwEahi68k/kqN2mfepjkGrG9Un70cgdRRKLKY6Rf8P8uvP2NvZktaQZNF3YE7agEkLj0vGK9bA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/rc-cascader": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz", + "integrity": "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "^2.3.1", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-checkbox": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.5.0.tgz", + "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.25.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.9.0.tgz", + "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/portal": "^1.0.0-8", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.3.0.tgz", + "integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@rc-component/portal": "^1.1.1", + "classnames": "^2.2.6", + "rc-motion": "^2.6.1", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dropdown": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz", + "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-util": "^5.44.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-field-form": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.1.tgz", + "integrity": "sha512-vKeSifSJ6HoLaAB+B8aq/Qgm8a3dyxROzCtKNCsBQgiverpc4kWDQihoUwzUj+zNWJOykwSY4dNX3QrGwtVb9A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/async-validator": "^5.0.3", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-image": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.12.0.tgz", + "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/portal": "^1.0.2", + "classnames": "^2.2.6", + "rc-dialog": "~9.6.0", + "rc-motion": "^2.6.2", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.8.0.tgz", + "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.18.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-input-number": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.5.0.tgz", + "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/mini-decimal": "^1.0.1", + "classnames": "^2.2.5", + "rc-input": "~1.8.0", + "rc-util": "^5.40.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.20.0.tgz", + "integrity": "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.22.5", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-input": "~1.8.0", + "rc-menu": "~9.16.0", + "rc-textarea": "~1.10.0", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.16.1", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.1.tgz", + "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.3.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.44.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-notification": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.4.tgz", + "integrity": "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.9.0", + "rc-util": "^5.20.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.5.0.tgz", + "integrity": "sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.37.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-pagination": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-5.1.0.tgz", + "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.11.3.tgz", + "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.43.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/rc-progress": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.1.tgz", + "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", + "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.44.1", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.7.1.tgz", + "integrity": "sha512-izj1Nw/Dw2Vb7EVr+D/E9lUTkBe+kKC+SAFSU9zqr7WV2W5Ktaa9Gc7cB2jTqgk8GROJayltaec+DBlYKc6d+g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-motion": "^2.4.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-select": { + "version": "14.16.8", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.8.tgz", + "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.1.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.3.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-slider": { + "version": "11.1.9", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.9.tgz", + "integrity": "sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-6.0.1.tgz", + "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.16.7", + "classnames": "^2.2.3", + "rc-util": "^5.16.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz", + "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "classnames": "^2.2.1", + "rc-util": "^5.30.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table": { + "version": "7.54.0", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.54.0.tgz", + "integrity": "sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/context": "^1.4.0", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.44.3", + "rc-virtual-list": "^3.14.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.7.0.tgz", + "integrity": "sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.6.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.34.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.10.2.tgz", + "integrity": "sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-input": "~1.8.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tooltip": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.4.0.tgz", + "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.1", + "rc-util": "^5.44.3" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tree": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.13.1.tgz", + "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.27.0.tgz", + "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "2.x", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-upload": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.11.0.tgz", + "integrity": "sha512-ZUyT//2JAehfHzjWowqROcwYJKnZkIUGWaTE/VogVrepSl7AFNbQf4+zGfX4zl9Vrj/Jm8scLO0R6UlPDKK4wA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.44.4", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-virtual-list": { + "version": "3.19.2", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.19.2.tgz", + "integrity": "sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, + "node_modules/svg-path-parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/svg-path-parser/-/svg-path-parser-1.1.0.tgz", + "integrity": "sha512-jGCUqcQyXpfe38R7RFfhrMyfXcBmpMNJI/B+4CE9/Unkh98UporAc461GTthv+TVDuZXsBx7/WiwJb1Oh4tt4A==", + "license": "MIT" + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/frontend/admin/src/App.tsx b/frontend/admin/src/App.tsx index 8a53d06..65bb38a 100644 --- a/frontend/admin/src/App.tsx +++ b/frontend/admin/src/App.tsx @@ -1,18 +1,22 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' import Login from './pages/Login' import Layout from './components/Layout' -import UtteranceList from './pages/UtteranceList' import Dashboard from './pages/Dashboard' +import UtteranceList from './pages/UtteranceList' +import KnowledgeList from './pages/KnowledgeList' +import AdminList from './pages/AdminList' function App() { return ( - + } /> }> } /> } /> } /> + } /> + } /> diff --git a/frontend/admin/src/components/Layout.tsx b/frontend/admin/src/components/Layout.tsx index 75d29cd..a22263a 100644 --- a/frontend/admin/src/components/Layout.tsx +++ b/frontend/admin/src/components/Layout.tsx @@ -1,34 +1,55 @@ -import { Layout as AntLayout, Menu, Button } from 'antd' +import { useEffect, useState } from 'react' import { Outlet, useNavigate, useLocation } from 'react-router-dom' -import { DashboardOutlined, CommentOutlined, LogoutOutlined } from '@ant-design/icons' +import { Layout as AntLayout, Menu, Button, message } from 'antd' +import { DashboardOutlined, CommentOutlined, BookOutlined, TeamOutlined } from '@ant-design/icons' -const { Sider, Content, Header } = AntLayout +const { Header, Sider, Content } = AntLayout export default function Layout() { const navigate = useNavigate() const location = useLocation() + const [collapsed, setCollapsed] = useState(false) + + useEffect(() => { + const token = localStorage.getItem('admin_token') + if (!token) { + navigate('/login') + } + }, [navigate]) const menuItems = [ { key: '/dashboard', icon: , label: '数据看板' }, - { key: '/utterances', icon: , label: '话术库管理' } + { key: '/utterances', icon: , label: '话术库管理' }, + { key: '/knowledge', icon: , label: '知识库管理' }, + { key: '/admins', icon: , label: '管理员账号' }, ] const handleLogout = () => { localStorage.removeItem('admin_token') + localStorage.removeItem('admin_info') + message.success('已退出登录') navigate('/login') } return ( - -
+ +
AI坐席助手
- navigate(e.key)} /> + navigate(key)} + /> -
- +
+ + {menuItems.find(i => i.key === location.pathname)?.label || '后台管理'} + +
diff --git a/frontend/admin/src/pages/AdminList.tsx b/frontend/admin/src/pages/AdminList.tsx new file mode 100644 index 0000000..9bae745 --- /dev/null +++ b/frontend/admin/src/pages/AdminList.tsx @@ -0,0 +1,102 @@ +import { useState, useEffect } from 'react' +import { Table, Button, Tag, Space, Modal, Form, Input, Select, message, Popconfirm } from 'antd' +import request from '../utils/request' + +export default function AdminList() { + const [data, setData] = useState([]) + const [loading, setLoading] = useState(false) + const [modalVisible, setModalVisible] = useState(false) + const [editing, setEditing] = useState(null) + const [form] = Form.useForm() + const [pagination, setPagination] = useState({ current: 1, pageSize: 20, total: 0 }) + + const currentAdmin = JSON.parse(localStorage.getItem('admin_info') || '{}') + + const loadData = async (page = 1, size = 20) => { + setLoading(true) + try { + const res: any = await request.get('/v1/admin/admins', { params: { page, size } }) + setData(res.data.records || []) + setPagination({ current: page, pageSize: size, total: res.data.total || 0 }) + } finally { + setLoading(false) + } + } + + useEffect(() => { + loadData() + }, []) + + const handleSave = async () => { + const values = await form.validateFields() + try { + if (editing) { + await request.put(`/v1/admin/admins/${editing.id}`, values) + message.success('更新成功') + } else { + await request.post('/v1/admin/admins', values) + message.success('创建成功') + } + setModalVisible(false) + loadData(pagination.current) + } catch (e) {} + } + + const handleDelete = async (id: number) => { + if (id === currentAdmin.adminId) { + message.error('不能删除自己') + return + } + try { + await request.delete(`/v1/admin/admins/${id}`) + message.success('删除成功') + loadData(pagination.current) + } catch (e) {} + } + + const columns = [ + { title: 'ID', dataIndex: 'id', width: 80 }, + { title: '用户名', dataIndex: 'username' }, + { title: '姓名', dataIndex: 'name' }, + { title: '角色', dataIndex: 'role', render: (r: string) => {r === 'SUPER_ADMIN' ? '超管' : '管理员'} }, + { title: '状态', dataIndex: 'status', render: (s: string) => {s} }, + { title: '最后登录', dataIndex: 'lastLoginTime', width: 160 }, + { title: '操作', width: 140, render: (_: any, record: any) => ( + + + handleDelete(record.id)}> + + + + )} + ] + + return ( +
+
+

管理员账号

+ +
+ loadData(p, s) }} /> + setModalVisible(false)} onOk={handleSave}> +
+ + + + + + + + + + + + + +
+ + ) +} diff --git a/frontend/admin/src/pages/Dashboard.tsx b/frontend/admin/src/pages/Dashboard.tsx index 3c6b1de..adc4728 100644 --- a/frontend/admin/src/pages/Dashboard.tsx +++ b/frontend/admin/src/pages/Dashboard.tsx @@ -1,32 +1,320 @@ -import { Card, Statistic, Row, Col } from 'antd' -import { ArrowUpOutlined, MessageOutlined, UserOutlined, LikeOutlined } from '@ant-design/icons' +import { useEffect, useState } from 'react' +import { + Card, Statistic, Row, Col, Table, Tag, Radio, message, Spin, Progress +} from 'antd' +import { + ArrowUpOutlined, MessageOutlined, UserOutlined, LikeOutlined, + ClockCircleOutlined, TeamOutlined, CommentOutlined, TrophyOutlined +} from '@ant-design/icons' +import type { ColumnsType } from 'antd/es/table' + +interface DashboardData { + todayConversations: number + todayMessages: number + activeStaff: number + utteranceAdoptionRate: number + conversionRate: number + avgResponseTimeMs: number +} + +interface UtterancePerf { + utteranceId: string + title: string + totalShow: number + totalClick: number + totalSend: number + totalPositive: number + totalConversion: number + ctr: number + conversionRate: number + avgScore: number +} + +interface StaffPerf { + staffId: string + staffName: string + conversationCount: number + totalRounds: number + totalIntents: number + avgIdleDuration: number + uniqueCustomers: number + lastActiveTime: string +} export default function Dashboard() { + const [loading, setLoading] = useState(false) + const [days, setDays] = useState(7) + const [dashboard, setDashboard] = useState(null) + const [utterancePerf, setUtterancePerf] = useState([]) + const [staffPerf, setStaffPerf] = useState([]) + + const corpId = 'wwd483c2fba24ae30a' + + useEffect(() => { + loadData() + }, [days]) + + const loadData = async () => { + setLoading(true) + try { + const token = localStorage.getItem('admin_token') || '' + const headers = { 'Authorization': 'Bearer ' + token } + + // 并行请求三个接口 + const [dashRes, utterRes, staffRes] = await Promise.all([ + fetch(`/api/v1/analytics/dashboard?corpId=${corpId}`, { headers }), + fetch(`/api/v1/analytics/utterance-performance?corpId=${corpId}&days=${days}`, { headers }), + fetch(`/api/v1/analytics/staff-performance?corpId=${corpId}&days=${days}`, { headers }) + ]) + + const dashData = await dashRes.json() + const utterData = await utterRes.json() + const staffData = await staffRes.json() + + if (dashData.code === 0) setDashboard(dashData.data) + if (utterData.code === 0) setUtterancePerf(utterData.data || []) + if (staffData.code === 0) setStaffPerf(staffData.data || []) + } catch (e) { + message.error('加载数据失败') + } finally { + setLoading(false) + } + } + + const formatMs = (ms: number) => { + if (ms < 1000) return `${Math.round(ms)}ms` + return `${(ms / 1000).toFixed(1)}s` + } + + const utteranceColumns: ColumnsType = [ + { + title: '话术', + dataIndex: 'title', + key: 'title', + ellipsis: true, + render: (text: string, record: UtterancePerf) => ( +
+
{text}
+
{record.utteranceId}
+
+ ) + }, + { + title: '展示', + dataIndex: 'totalShow', + key: 'totalShow', + width: 80, + align: 'center' + }, + { + title: '点击', + dataIndex: 'totalClick', + key: 'totalClick', + width: 80, + align: 'center' + }, + { + title: '发送', + dataIndex: 'totalSend', + key: 'totalSend', + width: 80, + align: 'center' + }, + { + title: '点击率', + dataIndex: 'ctr', + key: 'ctr', + width: 100, + align: 'center', + render: (v: number) => ( + + ) + }, + { + title: '转化率', + dataIndex: 'conversionRate', + key: 'conversionRate', + width: 100, + align: 'center', + render: (v: number) => ( + 0.15 ? 'green' : v > 0.1 ? 'orange' : 'default'}> + {(v * 100).toFixed(1)}% + + ) + }, + { + title: '综合评分', + dataIndex: 'avgScore', + key: 'avgScore', + width: 100, + align: 'center', + render: (v: number) => ( + 0.4 ? '#52c41a' : '#1677ff' }}> + {(v * 100).toFixed(1)} + + ) + } + ] + + const staffColumns: ColumnsType = [ + { + title: '坐席', + dataIndex: 'staffName', + key: 'staffName', + render: (text: string, record: StaffPerf) => ( +
+
{text}
+
{record.staffId}
+
+ ) + }, + { + title: '会话数', + dataIndex: 'conversationCount', + key: 'conversationCount', + width: 90, + align: 'center', + sorter: (a: StaffPerf, b: StaffPerf) => a.conversationCount - b.conversationCount + }, + { + title: '对话轮次', + dataIndex: 'totalRounds', + key: 'totalRounds', + width: 100, + align: 'center' + }, + { + title: '意向识别', + dataIndex: 'totalIntents', + key: 'totalIntents', + width: 100, + align: 'center' + }, + { + title: '服务客户', + dataIndex: 'uniqueCustomers', + key: 'uniqueCustomers', + width: 100, + align: 'center' + }, + { + title: '平均响应', + dataIndex: 'avgIdleDuration', + key: 'avgIdleDuration', + width: 110, + align: 'center', + render: (v: number) => formatMs(v) + }, + { + title: '最近活跃', + dataIndex: 'lastActiveTime', + key: 'lastActiveTime', + width: 160, + render: (v: string) => v ? new Date(v).toLocaleString() : '-' + } + ] + return ( -
-

数据看板

- -
- - } /> - - - - - } /> - - - - - } valueStyle={{ color: '#3f8600' }} /> - - - - - } valueStyle={{ color: '#cf1322' }} /> - - - - + +
+
+

数据看板

+ setDays(e.target.value)}> + 近7天 + 近30天 + +
+ + {/* 核心指标卡片 */} + +
+ + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + valueStyle={{ color: '#3f8600' }} + /> + + + + + } + valueStyle={{ color: '#cf1322' }} + /> + + + + + } + /> + + + + + {/* 话术绩效排行 */} + 话术绩效排行(近{days}天)} + style={{ marginTop: 24 }} + > +
+ + + {/* 员工绩效排行 */} + 坐席绩效排行(近{days}天)} + style={{ marginTop: 24 }} + > +
+ + + ) } diff --git a/frontend/admin/src/pages/KnowledgeList.tsx b/frontend/admin/src/pages/KnowledgeList.tsx new file mode 100644 index 0000000..0d21487 --- /dev/null +++ b/frontend/admin/src/pages/KnowledgeList.tsx @@ -0,0 +1,115 @@ +import { useState, useEffect } from 'react' +import { Table, Button, Tag, Space, Modal, Form, Input, Upload, message, Popconfirm } from 'antd' +import { UploadOutlined } from '@ant-design/icons' +import request from '../utils/request' + +export default function KnowledgeList() { + const [data, setData] = useState([]) + const [loading, setLoading] = useState(false) + const [modalVisible, setModalVisible] = useState(false) + const [previewVisible, setPreviewVisible] = useState(false) + const [previewDoc, setPreviewDoc] = useState(null) + const [form] = Form.useForm() + const [pagination, setPagination] = useState({ current: 1, pageSize: 20, total: 0 }) + + const loadData = async (page = 1, size = 20) => { + setLoading(true) + try { + const res: any = await request.get('/v1/admin/knowledge', { params: { page, size } }) + setData(res.data.records || []) + setPagination({ current: page, pageSize: size, total: res.data.total || 0 }) + } finally { + setLoading(false) + } + } + + useEffect(() => { + loadData() + }, []) + + const handleUpload = async (options: any) => { + const { file, onSuccess, onError } = options + const formData = new FormData() + formData.append('file', file) + try { + const res: any = await request.post('/v1/admin/knowledge/upload', formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }) + message.success('上传成功') + onSuccess?.(res) + loadData(pagination.current) + } catch (e) { + onError?.(e) + } + } + + const handleSave = async () => { + const values = await form.validateFields() + try { + await request.post('/v1/admin/knowledge', values) + message.success('创建成功') + setModalVisible(false) + loadData(pagination.current) + } catch (e) {} + } + + const handleDelete = async (id: number) => { + try { + await request.delete(`/v1/admin/knowledge/${id}`) + message.success('删除成功') + loadData(pagination.current) + } catch (e) {} + } + + const columns = [ + { title: '文档ID', dataIndex: 'docId', width: 180 }, + { title: '标题', dataIndex: 'title', ellipsis: true, render: (t: string, record: any) => ( + { setPreviewDoc(record); setPreviewVisible(true) }}>{t} + )}, + { title: '类型', dataIndex: 'docType', width: 100, render: (t: string) => {t} }, + { title: '分类', dataIndex: 'category', width: 120 }, + { title: '状态', dataIndex: 'status', width: 80, render: (s: string) => {s} }, + { title: '创建时间', dataIndex: 'createdAt', width: 160 }, + { title: '操作', width: 120, render: (_: any, record: any) => ( + handleDelete(record.id)}> + + + )} + ] + + return ( +
+
+

知识库管理

+ + + + + + +
+
loadData(p, s) }} /> + + setModalVisible(false)} onOk={handleSave} width={700}> +
+ + + + + + + + + + +
+ + setPreviewVisible(false)} footer={null} width={800}> +

{previewDoc?.title}

+
+          {previewDoc?.contentText || previewDoc?.content || '(无内容)'}
+        
+
+ + ) +} diff --git a/frontend/admin/src/pages/Login.tsx b/frontend/admin/src/pages/Login.tsx index fdb5f5c..e45dbdf 100644 --- a/frontend/admin/src/pages/Login.tsx +++ b/frontend/admin/src/pages/Login.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' -import { Form, Input, Button, Card, message } from 'antd' import { useNavigate } from 'react-router-dom' +import { Form, Input, Button, Card, message } from 'antd' +import request from '../utils/request' export default function Login() { const [loading, setLoading] = useState(false) @@ -9,29 +10,33 @@ export default function Login() { const onFinish = async (values: any) => { setLoading(true) try { - if (values.username === 'admin' && values.password === 'admin123') { - localStorage.setItem('admin_token', 'mock_token') - message.success('登录成功') - navigate('/dashboard') - } else { - message.error('用户名或密码错误') - } + const res: any = await request.post('/v1/admin/auth/login', values) + localStorage.setItem('admin_token', res.data.token) + localStorage.setItem('admin_info', JSON.stringify(res.data)) + message.success('登录成功') + navigate('/dashboard') + } catch (e) { + // error handled by interceptor } finally { setLoading(false) } } return ( -
- -
- - +
+ + + + - - + + + + + -
diff --git a/frontend/admin/src/pages/UtteranceList.tsx b/frontend/admin/src/pages/UtteranceList.tsx index bc013f2..46f23f4 100644 --- a/frontend/admin/src/pages/UtteranceList.tsx +++ b/frontend/admin/src/pages/UtteranceList.tsx @@ -1,35 +1,67 @@ import { useState, useEffect } from 'react' -import { Table, Button, Tag, Space, Modal, Form, Input, Select, message } from 'antd' +import { Table, Button, Tag, Space, Modal, Form, Input, Select, message, Popconfirm } from 'antd' +import request from '../utils/request' export default function UtteranceList() { const [data, setData] = useState([]) const [loading, setLoading] = useState(false) const [modalVisible, setModalVisible] = useState(false) + const [editing, setEditing] = useState(null) const [form] = Form.useForm() + const [pagination, setPagination] = useState({ current: 1, pageSize: 20, total: 0 }) + + const loadData = async (page = 1, size = 20) => { + setLoading(true) + try { + const res: any = await request.get('/v1/admin/utterances', { params: { page, size } }) + setData(res.data.records || []) + setPagination({ current: page, pageSize: size, total: res.data.total || 0 }) + } finally { + setLoading(false) + } + } useEffect(() => { loadData() }, []) - const loadData = () => { - setLoading(true) - // TODO: 调用真实API - setData([ - { id: 1, utteranceId: 'UTT-S01-001', title: '首次接待通用开场白', stageTags: '开场白', status: 'ACTIVE', successRate: 0.85 }, - { id: 2, utteranceId: 'UTT-S03-001', title: '推荐3D场景UE地编就业班', stageTags: '课程推荐', status: 'ACTIVE', successRate: 0.72 } - ]) - setLoading(false) + const handleSave = async () => { + const values = await form.validateFields() + try { + if (editing) { + await request.put(`/v1/admin/utterances/${editing.id}`, values) + message.success('更新成功') + } else { + await request.post('/v1/admin/utterances', values) + message.success('创建成功') + } + setModalVisible(false) + loadData(pagination.current) + } catch (e) { + // handled by interceptor + } + } + + const handleDelete = async (id: number) => { + try { + await request.delete(`/v1/admin/utterances/${id}`) + message.success('删除成功') + loadData(pagination.current) + } catch (e) {} } const columns = [ - { title: '话术ID', dataIndex: 'utteranceId' }, - { title: '标题', dataIndex: 'title' }, - { title: '阶段', dataIndex: 'stageTags', render: (t: string) => {t} }, - { title: '状态', dataIndex: 'status', render: (s: string) => {s} }, - { title: '成功率', dataIndex: 'successRate', render: (v: number) => `${(v * 100).toFixed(1)}%` }, - { title: '操作', render: (_: any, record: any) => ( + { title: '话术ID', dataIndex: 'utteranceId', width: 140 }, + { title: '标题', dataIndex: 'title', ellipsis: true }, + { title: '阶段', dataIndex: 'stageTags', width: 120, render: (t: string) => {t?.replace(/["\[\]]/g, '')} }, + { title: '状态', dataIndex: 'status', width: 80, render: (s: string) => {s} }, + { title: '成功率', dataIndex: 'successRate', width: 90, render: (v: number) => v ? `${(v * 100).toFixed(1)}%` : '-' }, + { title: '操作', width: 140, render: (_: any, record: any) => ( - + + handleDelete(record.id)}> + + )} ] @@ -38,29 +70,50 @@ export default function UtteranceList() {

话术库管理

- +
-
- setModalVisible(false)} onOk={() => { message.success('保存成功'); setModalVisible(false) }}> +
loadData(p, s) }} + /> + setModalVisible(false)} onOk={handleSave} width={700}>
+ + + - + - - + + + + + ({ value: i + 1, label: `${i + 1}` }))} /> +
diff --git a/frontend/admin/src/utils/request.ts b/frontend/admin/src/utils/request.ts new file mode 100644 index 0000000..3e3be0e --- /dev/null +++ b/frontend/admin/src/utils/request.ts @@ -0,0 +1,46 @@ +import axios from 'axios' +import { message } from 'antd' + +const request = axios.create({ + baseURL: '/api', + timeout: 30000, +}) + +request.interceptors.request.use( + (config) => { + const token = localStorage.getItem('admin_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => { + return Promise.reject(error) + } +) + +request.interceptors.response.use( + (response) => { + const data = response.data + if (data.code !== 0) { + message.error(data.message || '请求失败') + return Promise.reject(new Error(data.message)) + } + // 统一分页字段:后端返回 list,前端使用 records + if (data.data && data.data.list && !data.data.records) { + data.data.records = data.data.list + } + return data + }, + (error) => { + if (error.response?.status === 401) { + localStorage.removeItem('admin_token') + window.location.href = '/login' + } else { + message.error(error.message || '网络错误') + } + return Promise.reject(error) + } +) + +export default request diff --git a/frontend/admin/tsconfig.json b/frontend/admin/tsconfig.json new file mode 100644 index 0000000..3934b8f --- /dev/null +++ b/frontend/admin/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/frontend/admin/tsconfig.node.json b/frontend/admin/tsconfig.node.json new file mode 100644 index 0000000..59c6ed8 --- /dev/null +++ b/frontend/admin/tsconfig.node.json @@ -0,0 +1 @@ +{"compilerOptions":{"composite":true,"skipLibCheck":true,"module":"ESNext","moduleResolution":"bundler","allowSyntheticDefaultImports":true},"include":["vite.config.ts"]} diff --git a/frontend/admin/vite.config.ts b/frontend/admin/vite.config.ts index e63ca68..3e85a71 100644 --- a/frontend/admin/vite.config.ts +++ b/frontend/admin/vite.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ + base: '/admin/', plugins: [react()], server: { port: 5174, diff --git a/frontend/sidebar/.gitignore b/frontend/sidebar/.gitignore new file mode 100644 index 0000000..2c4cd1a --- /dev/null +++ b/frontend/sidebar/.gitignore @@ -0,0 +1,25 @@ +# 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? +node_modules/ \ No newline at end of file diff --git a/frontend/sidebar/Dockerfile b/frontend/sidebar/Dockerfile index 4e1c6d9..bd8ce76 100644 --- a/frontend/sidebar/Dockerfile +++ b/frontend/sidebar/Dockerfile @@ -6,6 +6,6 @@ COPY . . RUN npm run build FROM nginx:alpine -COPY --from=builder /app/dist /usr/share/nginx/html/sidebar +COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 diff --git a/frontend/sidebar/nginx.conf b/frontend/sidebar/nginx.conf new file mode 100644 index 0000000..3599b59 --- /dev/null +++ b/frontend/sidebar/nginx.conf @@ -0,0 +1,25 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # API 代理到 Gateway + location /api/ { + proxy_pass http://gateway:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # 静态资源 + location / { + try_files $uri $uri/ /index.html; + } + + # 缓存静态资源 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1d; + add_header Cache-Control "public, immutable"; + } +} diff --git a/frontend/sidebar/package-lock.json b/frontend/sidebar/package-lock.json new file mode 100644 index 0000000..89bc071 --- /dev/null +++ b/frontend/sidebar/package-lock.json @@ -0,0 +1,3473 @@ +{ + "name": "ai-assistant-sidebar", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ai-assistant-sidebar", + "version": "1.0.0", + "dependencies": { + "antd-mobile": "^5.33.0", + "axios": "^1.6.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "zustand": "^4.4.0" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.0.0", + "puppeteer-core": "^24.43.1", + "typescript": "^5.0.0", + "vite": "^5.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.2.tgz", + "integrity": "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.3", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.4", + "tar-fs": "^3.1.1", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.3.tgz", + "integrity": "sha512-bk/FJ09fLf+NLODMAFll6CfYrHPBioTedhW6lxDBuuWucJEqFUd4l/D/5JgIi3dina6sYahB8iuPAZTNz2pMxw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@react-spring/animated": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.6.1.tgz", + "integrity": "sha512-ls/rJBrAqiAYozjLo5EPPLLOb1LM0lNVQcXODTC1SMtS6DbuBCPaKco5svFUQFMP2dso3O+qcC4k9FsKc0KxMQ==", + "license": "MIT", + "dependencies": { + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/core": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.6.1.tgz", + "integrity": "sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ==", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~9.6.1", + "@react-spring/rafz": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/rafz": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.6.1.tgz", + "integrity": "sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ==", + "license": "MIT" + }, + "node_modules/@react-spring/shared": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.6.1.tgz", + "integrity": "sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw==", + "license": "MIT", + "dependencies": { + "@react-spring/rafz": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/types": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.6.1.tgz", + "integrity": "sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==", + "license": "MIT" + }, + "node_modules/@react-spring/web": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.6.1.tgz", + "integrity": "sha512-X2zR6q2Z+FjsWfGAmAXlQaoUHbPmfuCaXpuM6TcwXPpLE1ZD4A1eys/wpXboFQmDkjnrlTmKvpVna1MjWpZ5Hw==", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~9.6.1", + "@react-spring/core": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.7.0.tgz", + "integrity": "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "undici-types": "~7.21.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@use-gesture/core": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.0.tgz", + "integrity": "sha512-rh+6MND31zfHcy9VU3dOZCqGY511lvGcfyJenN4cWZe0u1BH6brBpBddLVXhF2r4BMqWbvxfsbL7D287thJU2A==", + "license": "MIT" + }, + "node_modules/@use-gesture/react": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.0.tgz", + "integrity": "sha512-3zc+Ve99z4usVP6l9knYVbVnZgfqhKah7sIG+PS2w+vpig2v2OLct05vs+ZXMzwxdNCMka8B+8WlOo0z6Pn6DA==", + "license": "MIT", + "dependencies": { + "@use-gesture/core": "10.3.0" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ahooks": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/ahooks/-/ahooks-3.9.7.tgz", + "integrity": "sha512-S0lvzhbdlhK36RFBkGv+RbOM/dbbweym+BIHM/bwwuWVSVN5TuVErHPMWo4w0t1NDYg5KPp2iEf7Y7E5LASYiw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@types/js-cookie": "^3.0.6", + "dayjs": "^1.9.1", + "intersection-observer": "^0.12.0", + "js-cookie": "^3.0.5", + "lodash": "^4.17.21", + "react-fast-compare": "^3.2.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.0.0", + "tslib": "^2.4.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/antd-mobile": { + "version": "5.42.3", + "resolved": "https://registry.npmjs.org/antd-mobile/-/antd-mobile-5.42.3.tgz", + "integrity": "sha512-HLykA0Cr2am7dgEch/TRUnr4GHgTOM59rA9CVjgBJVVJ8dk2w8uUBz//Bji19sRH2leiUObWY7KQZpsWF+WQeg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.4.2", + "@rc-component/mini-decimal": "^1.1.0", + "@react-spring/web": "~9.6.1", + "@use-gesture/react": "10.3.0", + "ahooks": "^3.7.6", + "antd-mobile-icons": "^0.3.0", + "antd-mobile-v5-count": "^1.0.1", + "classnames": "^2.3.2", + "dayjs": "^1.11.7", + "deepmerge": "^4.3.1", + "nano-memoize": "^3.0.16", + "rc-field-form": "^1.34.2", + "rc-segmented": "~2.4.1", + "rc-util": "^5.44.4", + "react-fast-compare": "^3.2.2", + "react-is": "^18.2.0", + "runes2": "^1.1.2", + "staged-components": "^1.1.3", + "tslib": "^2.5.0", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/antd-mobile-icons": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/antd-mobile-icons/-/antd-mobile-icons-0.3.0.tgz", + "integrity": "sha512-rqINQpJWZWrva9moCd1Ye695MZYWmqLPE+bY8d2xLRy7iSQwPsinCdZYjpUPp2zL/LnKYSyXxP2ut2A+DC+whQ==", + "license": "MIT" + }, + "node_modules/antd-mobile-v5-count": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/antd-mobile-v5-count/-/antd-mobile-v5-count-1.0.1.tgz", + "integrity": "sha512-YGsiEDCPUDz3SzfXi6gLZn/HpeSMW+jgPc4qiYUr1fSopg3hkUie2TnooJdExgfiETHefH3Ggs58He0OVfegLA==", + "license": "MIT" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz", + "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz", + "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", + "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.3.tgz", + "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz", + "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chromium-bidi": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz", + "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1608973", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1608973.tgz", + "integrity": "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.354", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.354.tgz", + "integrity": "sha512-JaBHwWcfIdmSAfWM5l3uwjGd431j8YEMikZ+K/2nXVuBqJKyZ0f+2h4n4JY5AyNiZmnY9qQr2RU3v9DxDmHMNg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/intersection-observer": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz", + "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==", + "deprecated": "The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019.", + "license": "Apache-2.0" + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nano-memoize": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/nano-memoize/-/nano-memoize-3.0.16.tgz", + "integrity": "sha512-JyK96AKVGAwVeMj3MoMhaSXaUNqgMbCRSQB3trUV8tYZfWEzqUBKdK1qJpfuNXgKeHOx1jv/IEYTM659ly7zUA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.44", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz", + "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-agent/node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer-core": { + "version": "24.43.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.43.1.tgz", + "integrity": "sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.13.2", + "chromium-bidi": "14.0.0", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1608973", + "typed-query-selector": "^2.12.2", + "webdriver-bidi-protocol": "0.4.1", + "ws": "^8.20.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/rc-field-form": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.44.0.tgz", + "integrity": "sha512-el7w87fyDUsca63Y/s8qJcq9kNkf/J5h+iTdqG5WsSHLH0e6Usl7QuYSmSVzJMgtp40mOVZIY/W/QP9zwrp1FA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "async-validator": "^4.1.0", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.44.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.4.1.tgz", + "integrity": "sha512-KUi+JJFdKnumV9iXlm+BJ00O4NdVBp2TEexLCk6bK1x/RH83TvYKQMzIz/7m3UTRPD08RM/8VG/JNjWgWbd4cw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-motion": "^2.4.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-util": { + "version": "5.44.4", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/runes2": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/runes2/-/runes2-1.1.4.tgz", + "integrity": "sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/screenfull": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", + "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/staged-components": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/staged-components/-/staged-components-1.1.3.tgz", + "integrity": "sha512-9EIswzDqjwlEu+ymkV09TTlJfzSbKgEnNteUnZSTxkpMgr5Wx2CzzA9WcMFWBNCldqVPsHVnRGGrApduq2Se5A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/streamx": { + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", + "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", + "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/tar-stream/node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/text-decoder/node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typed-query-selector": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", + "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.21.0.tgz", + "integrity": "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz", + "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + } + } +} diff --git a/frontend/sidebar/package.json b/frontend/sidebar/package.json index 51e654d..bbd7b52 100644 --- a/frontend/sidebar/package.json +++ b/frontend/sidebar/package.json @@ -9,16 +9,17 @@ "preview": "vite preview" }, "dependencies": { + "antd-mobile": "^5.33.0", + "axios": "^1.6.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "antd-mobile": "^5.33.0", - "zustand": "^4.4.0", - "axios": "^1.6.0" + "zustand": "^4.4.0" }, "devDependencies": { "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.0.0", + "puppeteer-core": "^24.43.1", "typescript": "^5.0.0", "vite": "^5.0.0" } diff --git a/frontend/sidebar/public/wx-test.html b/frontend/sidebar/public/wx-test.html new file mode 100644 index 0000000..f02474f --- /dev/null +++ b/frontend/sidebar/public/wx-test.html @@ -0,0 +1,212 @@ + + + + + +企微 JS-SDK 测试页 + + + + +
+
环境检测 检测中
+
wx 对象:--
+
当前 URL:--
+
+ +
+
1. 签名接口测试
+ +
+ + +
+
+ +
+
2. wx.config 测试
+ +
+ +
+
3. API 调用测试
+
+ + +
+
+ +
+ +
+
4. OAuth 登录测试
+ +
+ +
+
日志输出
+ +
+
+ + + + diff --git a/frontend/sidebar/src/App.tsx b/frontend/sidebar/src/App.tsx index 499831c..9144a76 100644 --- a/frontend/sidebar/src/App.tsx +++ b/frontend/sidebar/src/App.tsx @@ -1,31 +1,146 @@ import { useEffect, useState } from 'react' -import { NavBar, Tabs } from 'antd-mobile' +import { NavBar, Tabs, Toast } from 'antd-mobile' import ProfileCard from './components/ProfileCard' import ScriptRecommend from './components/ScriptRecommend' +import ConversationAnalysis from './components/ConversationAnalysis' import './styles.css' function App() { const [activeTab, setActiveTab] = useState('recommend') + const [authState, setAuthState] = useState<'loading' | 'authed' | 'error'>('loading') + const [userInfo, setUserInfo] = useState(null) useEffect(() => { - // 初始化企微JS-SDK - if (typeof (window as any).wx !== 'undefined') { - console.log('WeChat JS-SDK loaded') - } + initWeCom() }, []) + const initWeCom = async () => { + try { + // 1. OAuth2 登录流程 + const urlParams = new URLSearchParams(window.location.search) + const code = urlParams.get('code') + // const state = urlParams.get('state') // 保留用于后续扩展 + + if (code) { + // 有 code,换取 token + const res = await fetch(`/api/v1/auth/login?code=${encodeURIComponent(code)}`, { method: 'POST' }) + const data = await res.json() + if (data.code === 0 && data.data) { + localStorage.setItem('staff_token', data.data.token) + setUserInfo(data.data) + setAuthState('authed') + } else { + setAuthState('error') + Toast.show({ content: '登录失败: ' + (data.message || '未知错误'), icon: 'fail' }) + } + } else { + // 无 code,检查本地 token + const token = localStorage.getItem('staff_token') + if (token) { + const res = await fetch('/api/v1/auth/info', { + headers: { Authorization: `Bearer ${token}` } + }) + const data = await res.json() + if (data.code === 0 && data.data) { + setUserInfo(data.data) + setAuthState('authed') + } else { + // token 过期,需要重新登录 + redirectToOAuth() + } + } else { + redirectToOAuth() + } + } + } catch (e) { + setAuthState('error') + Toast.show({ content: '初始化失败', icon: 'fail' }) + } + } + + const redirectToOAuth = () => { + // 在真实企微环境中,由企微容器自动注入 code + // 浏览器调试模式下,保持未登录状态,使用测试身份 + setAuthState('authed') + setUserInfo({ + userId: 'staff_001', + userName: '测试顾问', + corpId: 'wwd483c2fba24ae30a', + role: 'SEAT' + }) + } + + const initWxConfig = async () => { + const wx = (window as any).wx + if (!wx || !wx.config) return + + try { + const res = await fetch(`/api/v1/auth/signature?url=${encodeURIComponent(window.location.href.split('#')[0])}`) + const data = await res.json() + if (data.code !== 0 || !data.data) { + console.error('获取JS-SDK签名失败') + return + } + + const cfg = data.data + wx.config({ + beta: true, + debug: false, + appId: cfg.corpId, + timestamp: cfg.timestamp, + nonceStr: cfg.nonceStr, + signature: cfg.signature, + jsApiList: ['sendChatMessage', 'getContext', 'getCurExternalContact'] + }) + + wx.ready(() => { + console.log('JS-SDK 配置成功') + // 获取当前客户信息 + wx.invoke('getCurExternalContact', {}, (res: any) => { + if (res.err_msg === 'getCurExternalContact:ok') { + console.log('当前客户userId:', res.userId) + localStorage.setItem('current_customer_id', res.userId) + } + }) + }) + + wx.error((err: any) => { + console.error('JS-SDK 配置失败:', err) + }) + } catch (e) { + console.error('初始化JS-SDK异常:', e) + } + } + + useEffect(() => { + if (authState === 'authed') { + initWxConfig() + } + }, [authState]) + + if (authState === 'loading') { + return ( +
+
+
加载中...
+
正在连接企微
+
+
+ ) + } + return (
AI坐席助手 - + - + -
对话分析功能开发中...
+
diff --git a/frontend/sidebar/src/components/ConversationAnalysis.tsx b/frontend/sidebar/src/components/ConversationAnalysis.tsx new file mode 100644 index 0000000..4ed9626 --- /dev/null +++ b/frontend/sidebar/src/components/ConversationAnalysis.tsx @@ -0,0 +1,243 @@ +import { useEffect, useState } from 'react' +import { SpinLoading, Toast, Tag } from 'antd-mobile' + +interface Conversation { + sessionId: string + customerId: string + customerName: string + staffName: string + currentStage: string + stageConfidence: number + roundCount: number + intentCount: number + startTime: string + endTime: string + contextSummary: string + keyEvents: string +} + +interface ConversationTurn { + turnNumber: number + studentContent: string + seatContent: string + turnType: string + primaryIntent: string + intentConfidence: number + studentEmotion: string + createdAt: string +} + +interface Props { + userInfo?: any +} + +export default function ConversationAnalysis({ userInfo }: Props) { + const [conversations, setConversations] = useState([]) + const [selectedSession, setSelectedSession] = useState('') + const [turns, setTurns] = useState([]) + const [loading, setLoading] = useState(false) + const [loadingTurns, setLoadingTurns] = useState(false) + + const customerId = localStorage.getItem('current_customer_id') || 'wx_001' + const corpId = userInfo?.corpId || 'wwd483c2fba24ae30a' + + useEffect(() => { + loadConversations() + }, []) + + useEffect(() => { + if (selectedSession) { + loadTurns(selectedSession) + } + }, [selectedSession]) + + const loadConversations = async () => { + setLoading(true) + try { + const res = await fetch(`/api/v1/conversations?customerId=${customerId}&corpId=${corpId}`) + const data = await res.json() + if (data.code === 0 && data.data) { + setConversations(data.data) + if (data.data.length > 0) { + setSelectedSession(data.data[0].sessionId) + } + } + } catch (e) { + Toast.show({ content: '加载会话失败', icon: 'fail' }) + } finally { + setLoading(false) + } + } + + const loadTurns = async (sessionId: string) => { + setLoadingTurns(true) + try { + const res = await fetch(`/api/v1/conversations/${sessionId}/turns`) + const data = await res.json() + if (data.code === 0 && data.data) { + setTurns(data.data) + } + } catch (e) { + Toast.show({ content: '加载对话失败', icon: 'fail' }) + } finally { + setLoadingTurns(false) + } + } + + const getStageLabel = (stage?: string) => { + const map: Record = { + 'STAGE-01': '开场白', + 'STAGE-02': '需求挖掘', + 'STAGE-03': '方案呈现', + 'STAGE-04': '异议处理', + 'STAGE-05': '促成签约', + 'STAGE-06': '跟进维护', + 'STAGE-07': '转介绍', + 'STAGE-08': '沉默激活' + } + return map[stage || ''] || stage || '未知' + } + + const getEmotionLabel = (emotion?: string) => { + const map: Record = { + 'POSITIVE': { text: '积极', color: '#52c41a' }, + 'NEUTRAL': { text: '中性', color: '#faad14' }, + 'NEGATIVE': { text: '消极', color: '#ff4d4f' } + } + return map[emotion || ''] || { text: emotion || '未知', color: '#999' } + } + + const getTurnTypeLabel = (type?: string) => { + const map: Record = { + 'QUERY': '咨询', + 'OBJECTION': '异议', + 'INTEREST': '兴趣' + } + return map[type || ''] || type || '其他' + } + + if (loading) { + return ( +
+ +
+ ) + } + + const currentConv = conversations.find(c => c.sessionId === selectedSession) + + return ( +
+ {/* 会话选择器 */} + {conversations.length > 1 && ( +
+ {conversations.map(conv => ( +
setSelectedSession(conv.sessionId)} + style={{ + padding: '6px 12px', + borderRadius: 16, + fontSize: 12, + whiteSpace: 'nowrap', + cursor: 'pointer', + background: selectedSession === conv.sessionId ? '#1677ff' : '#f0f0f0', + color: selectedSession === conv.sessionId ? '#fff' : '#666' + }} + > + {new Date(conv.startTime).toLocaleDateString()} ({conv.roundCount}轮) +
+ ))} +
+ )} + + {/* 会话概览 */} + {currentConv && ( +
+
+ 会话概览 + {getStageLabel(currentConv.currentStage)} +
+
+
轮次: {currentConv.roundCount}
+
意图: {currentConv.intentCount}
+
置信度: {Math.round((currentConv.stageConfidence || 0) * 100)}%
+
+ {currentConv.contextSummary && ( +
+ 摘要: {currentConv.contextSummary} +
+ )} +
+ )} + + {/* 对话轮次 */} +
对话记录
+ + {loadingTurns ? ( +
+ ) : turns.length === 0 ? ( +
暂无对话记录
+ ) : ( +
+ {turns.map((turn) => { + const emotion = getEmotionLabel(turn.studentEmotion) + return ( +
+ {/* 轮次标识 */} +
+ 第{turn.turnNumber}轮 + + {emotion.text} + + + {getTurnTypeLabel(turn.turnType)} + + {turn.primaryIntent && ( + {turn.primaryIntent} + )} +
+ + {/* 学员消息 */} + {turn.studentContent && ( +
+
+
学员
+ {turn.studentContent} +
+
+ )} + + {/* 顾问回复 */} + {turn.seatContent && ( +
+
+
顾问
+ {turn.seatContent} +
+
+ )} +
+ ) + })} +
+ )} +
+ ) +} diff --git a/frontend/sidebar/src/components/ProfileCard.tsx b/frontend/sidebar/src/components/ProfileCard.tsx index 5d953fa..812fe3a 100644 --- a/frontend/sidebar/src/components/ProfileCard.tsx +++ b/frontend/sidebar/src/components/ProfileCard.tsx @@ -1,24 +1,104 @@ import { useEffect, useState } from 'react' -import { Badge, Tag } from 'antd-mobile' import type { CustomerProfile } from '../types' -export default function ProfileCard() { - const [profile, setProfile] = useState({ - customerId: 'wx_001', - name: '张同学', - studentType: '在校大学生', - skillLevel: '零基础', - intentLevel: '高', - intentScore: 75, - concernFocus: '就业导向型', - decisionStage: '方案比较', - interestedCourses: ['3D场景UE地编', '次世代角色模型'] - }) +interface Props { + userInfo?: any +} + +const DEFAULT_PROFILE: CustomerProfile = { + customerId: 'wx_001', + name: '张同学', + studentType: '在校大学生', + skillLevel: '零基础', + intentLevel: '高', + intentScore: 75, + concernFocus: '就业导向型', + decisionStage: '方案比较', + interestedCourses: ['3D场景UE地编', '次世代角色模型'] +} + +export default function ProfileCard({ userInfo }: Props) { + const [profile, setProfile] = useState(DEFAULT_PROFILE) + const [loading, setLoading] = useState(false) + const [manualCustomerId, setManualCustomerId] = useState('') useEffect(() => { - // TODO: 从API加载真实数据 + // 0. 优先从 URL 参数获取(调试模式) + const urlParams = new URLSearchParams(window.location.search) + const urlCustomerId = urlParams.get('customerId') + if (urlCustomerId && urlCustomerId !== '${userId}') { + console.log('调试模式,从URL获取客户ID:', urlCustomerId) + localStorage.setItem('current_customer_id', urlCustomerId) + loadCustomerProfile(urlCustomerId) + return + } + + // 1. 优先从企微JS-SDK获取当前客户信息 + const wx = (window as any).wx + if (wx && wx.invoke) { + wx.invoke('getCurExternalContact', {}, (res: any) => { + if (res && res.err_msg === 'getCurExternalContact:ok' && res.userId) { + console.log('当前客户:', res.userId) + localStorage.setItem('current_customer_id', res.userId) + loadCustomerProfile(res.userId) + } else { + // JS-SDK 获取失败(无权限),尝试从 localStorage 加载 + console.log('JS-SDK获取客户失败:', res?.err_msg || '无响应') + const cachedId = localStorage.getItem('current_customer_id') + if (cachedId && cachedId !== '${userId}') { + loadCustomerProfile(cachedId) + } + // 如果没有缓存,显示输入框(见下方手动输入逻辑) + } + }) + } else { + // 浏览器环境:直接加载默认或缓存的客户 + const cachedId = localStorage.getItem('current_customer_id') + if (cachedId && cachedId !== '${userId}') { + loadCustomerProfile(cachedId) + } + } }, []) + const loadCustomerProfile = async (customerId: string) => { + setLoading(true) + try { + const corpId = userInfo?.corpId || 'wwd483c2fba24ae30a' + const res = await fetch(`/api/v1/intent/profile/${customerId}?corpId=${corpId}`) + const data = await res.json() + if (data.code === 0 && data.data) { + const p = data.data + // 解析 interested_courses JSON 字符串 + let courses: string[] = [] + try { + courses = p.interestedCourses ? JSON.parse(p.interestedCourses) : [] + } catch (e) { + courses = [] + } + setProfile({ + customerId: p.customerId, + name: p.customerId === 'wx_001' ? '张同学' : (p.currentOccupation || '学员'), + studentType: p.studentType, + skillLevel: p.skillLevel, + intentLevel: p.intentLevel, + intentScore: p.intentScore != null ? Math.round(p.intentScore) : 50, + concernFocus: p.concernFocus, + decisionStage: p.decisionStage, + interestedCourses: courses, + budgetHint: p.budgetHint, + preferredCity: p.preferredCity, + age: p.age, + education: p.education, + currentOccupation: p.currentOccupation + }) + } + } catch (e) { + console.error('加载学员画像失败:', e) + } finally { + setLoading(false) + } + } + const getIntentColor = (level?: string) => { switch (level) { case '高': return '#52c41a' @@ -28,12 +108,46 @@ export default function ProfileCard() { } } + const handleManualLoad = () => { + if (manualCustomerId.trim()) { + localStorage.setItem('current_customer_id', manualCustomerId.trim()) + loadCustomerProfile(manualCustomerId.trim()) + } + } + return (
+ {/* 手动输入客户ID(当JS-SDK无权限时) */} + {profile.customerId === 'wx_001' && !loading && ( +
+
+ ⚠️ 未获取到客户ID(无客户联系权限) +
+
+ setManualCustomerId(e.target.value)} + style={{ flex: 1, padding: '6px 10px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 13 }} + onKeyPress={(e) => e.key === 'Enter' && handleManualLoad()} + /> + +
+
+ )}
{profile.name?.[0]}
-
-
{profile.name}
+
+
+ {profile.name} + {loading && 加载中...} +
意向度: {profile.intentLevel} ({profile.intentScore}分) @@ -52,6 +166,20 @@ export default function ProfileCard() { ))}
+ {/* 扩展信息 */} + {(profile.age || profile.education || profile.budgetHint || profile.preferredCity) && ( +
+ {profile.age && 年龄: {profile.age}岁} + {profile.education && 学历: {profile.education}} + {profile.budgetHint && 预算: {profile.budgetHint}} + {profile.preferredCity && 意向城市: {profile.preferredCity}} +
+ )} + {userInfo && ( +
+ 顾问: {userInfo.userName || userInfo.userId} | {userInfo.corpId} +
+ )}
) } diff --git a/frontend/sidebar/src/components/ScriptRecommend.tsx b/frontend/sidebar/src/components/ScriptRecommend.tsx index 63eadfb..864f6ba 100644 --- a/frontend/sidebar/src/components/ScriptRecommend.tsx +++ b/frontend/sidebar/src/components/ScriptRecommend.tsx @@ -1,10 +1,23 @@ import { useEffect, useState } from 'react' -import { Button, Toast, SpinLoading } from 'antd-mobile' +import { Button, Toast, SpinLoading, TextArea } from 'antd-mobile' import type { Recommendation } from '../types' -export default function ScriptRecommend() { +interface Props { + userInfo?: any +} + +export default function ScriptRecommend({ userInfo }: Props) { const [scripts, setScripts] = useState([]) const [loading, setLoading] = useState(false) + const [generating, setGenerating] = useState(false) + const [customerMsg, setCustomerMsg] = useState('我想学游戏美术,零基础可以吗?') + const [generated, setGenerated] = useState('') + + // 从 localStorage 或企微环境获取真实身份信息 + const sessionId = localStorage.getItem('current_session_id') || `session_${Date.now()}` + const customerId = localStorage.getItem('current_customer_id') || 'wx_001' + const staffId = userInfo?.userId || 'staff_001' + const corpId = userInfo?.corpId || 'wwd483c2fba24ae30a' useEffect(() => { loadRecommendations() @@ -13,50 +26,84 @@ export default function ScriptRecommend() { const loadRecommendations = async () => { setLoading(true) try { - // TODO: 调用真实API - // const res = await fetch('/api/v1/recommend/get?sessionId=xxx&...') - // const data = await res.json() - - // 模拟数据 - setScripts([ - { - utteranceId: 'UTT-S03-001', - title: '推荐3D场景UE地编就业班(零基础)', - content: '根据您的情况,我强烈推荐我们的【3D场景UE地编就业班】!\n\n为什么适合您:\n✅ 零基础入学:从软件操作开始教,无需任何基础\n✅ 六大模块系统学习\n✅ 5个月培训+2个月企业实训\n\n就业数据:往期学员平均薪资12-18K,就业推荐率95%+', - successRate: 0.72, - source: '规则匹配' - }, - { - utteranceId: 'UTT-S04-003', - title: '强调实战教学模式', - content: '我们的教学模式是业内独一无二的"5+2"模式:\n\n📚 5个月技能培训 + 2个月企业实训\n\n这意味着什么?\n✅ 毕业时您已经有真实项目经验\n✅ 实训期间进入合作企业,表现优秀可直接留用\n✅ 500+合作企业就业推荐网络', - successRate: 0.74, - source: '热门推荐' - } - ]) + const params = new URLSearchParams({ + sessionId, + customerId, + staffId, + corpId, + intentCode: 'INT-COURSE-01', + stage: 'STAGE-03', + studentType: '零基础', + message: customerMsg + }) + const res = await fetch(`/api/v1/recommend/get?${params.toString()}`, { method: 'POST' }) + const data = await res.json() + if (data.code === 0 && data.data) { + const list: Recommendation[] = data.data.map((item: any) => ({ + utteranceId: item.utteranceId, + title: item.title, + content: item.contentText || item.content, + successRate: item.successRate, + source: item.source || '推荐' + })) + setScripts(list) + } else { + Toast.show({ content: data.message || '加载失败', icon: 'fail' }) + } } catch (e) { - Toast.show({ content: '加载失败', icon: 'fail' }) + Toast.show({ content: '网络错误', icon: 'fail' }) } finally { setLoading(false) } } - const handleSend = (script: Recommendation) => { - // 调用企微JS-SDK发送消息 - if (typeof (window as any).wx !== 'undefined') { - (window as any).wx.invoke('sendChatMessage', { + const handleGenerate = async () => { + if (!customerMsg.trim()) { + Toast.show({ content: '请输入学员消息', icon: 'fail' }) + return + } + setGenerating(true) + try { + const params = new URLSearchParams({ + message: customerMsg, + intent: 'INT-COURSE-01', + stage: 'STAGE-03' + }) + const res = await fetch(`/api/v1/generation/utterance?${params.toString()}`, { method: 'POST' }) + const data = await res.json() + if (data.code === 0 && data.data) { + setGenerated(data.data.content || '生成失败,请稍后重试') + Toast.show({ content: '生成成功', icon: 'success' }) + } else { + Toast.show({ content: data.message || '生成失败', icon: 'fail' }) + } + } catch (e) { + Toast.show({ content: '网络错误', icon: 'fail' }) + } finally { + setGenerating(false) + } + } + + const handleSend = (content: string) => { + const wx = (window as any).wx + if (wx && wx.invoke) { + wx.invoke('sendChatMessage', { msgtype: 'text', - text: { content: script.content } + text: { content } }, (res: any) => { - if (res.err_msg === 'sendChatMessage:ok') { + console.log('sendChatMessage result:', res) + if (res && res.err_msg === 'sendChatMessage:ok') { Toast.show({ content: '发送成功', icon: 'success' }) - } else { + } else if (res && res.err_msg) { Toast.show({ content: '发送失败: ' + res.err_msg, icon: 'fail' }) + } else { + // JS-SDK 未正确初始化,回退到复制 + navigator.clipboard.writeText(content) + Toast.show({ content: '已复制到剪贴板(企微SDK未就绪)', icon: 'success' }) } }) } else { - // 浏览器调试环境,复制到剪贴板 - navigator.clipboard.writeText(script.content) + navigator.clipboard.writeText(content) Toast.show({ content: '已复制到剪贴板', icon: 'success' }) } } @@ -70,20 +117,75 @@ export default function ScriptRecommend() { } return ( -
+
+ {/* AI 生成区域 */} +
+
+ 🤖 AI 智能生成话术 +
+