diff --git a/Dockerfile-web b/Dockerfile-web index 0c4001f1..35c25ed2 100644 --- a/Dockerfile-web +++ b/Dockerfile-web @@ -7,23 +7,26 @@ COPY main/manager-web . RUN npm run build # 第二阶段:构建Java后端 -FROM maven:3-eclipse-temurin-21-alpine as api-builder +FROM maven:3.9.4-eclipse-temurin-21 as api-builder WORKDIR /app COPY main/manager-api/pom.xml . COPY main/manager-api/src ./src RUN mvn clean package -Dmaven.test.skip=true # 第三阶段:构建最终镜像 -FROM eclipse-temurin:21-jdk-jammy +FROM bellsoft/liberica-runtime-container:jre-21-glibc -# 安装Nginx并清理缓存 -RUN apt-get update && \ - apt-get install -y nginx && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* +# 安装Nginx和字体库 +RUN apk update && \ + apk add --no-cache nginx bash && \ + apk add --no-cache fontconfig ttf-dejavu msttcorefonts-installer && \ + rm -rf /var/cache/apk/* + +# 更新字体缓存 +RUN printf 'YES\n' | update-ms-fonts && fc-cache -f -v # 配置Nginx -COPY docs/docker/nginx.conf /etc/nginx/conf.d/default.conf +COPY docs/docker/nginx.conf /etc/nginx/nginx.conf # 复制前端构建产物 COPY --from=web-builder /app/dist /usr/share/nginx/html diff --git a/README.md b/README.md index 810ce514..4b3215f6 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,20 @@ [![Banners](docs/images/banner1.png)](https://github.com/xinnan-tech/xiaozhi-esp32-server) -

小智后端服务xiaozhi-esp32-server

本项目为开源智能硬件项目 xiaozhi-esp32提供后端服务
-根据小智通信协议使用Python实现
+根据小智通信协议使用Python、Java、Vue实现
帮助您快速搭建小智服务器

English -· 简体中文 +· 常见问题反馈问题部署文档 · 更新日志部署文档反馈问题

@@ -126,109 +125,71 @@ --- -## 系统要求与部署前提 🖥️ - -建议 4 核 CPU、8G 内存的电脑。如果开启ASR也使用API,可运行在2核CPU、2G内存的服务器中。[请参考部署架构图](./docs/images/deploy.png) - ---- - ## 警告 ⚠️ 1、本项目为开源软件,本软件与对接的任何第三方API服务商(包括但不限于语音识别、大模型、语音合成等平台)均不存在商业合作关系,不为其服务质量及资金安全提供任何形式的担保。 建议使用者优先选择持有相关业务牌照的服务商,并仔细阅读其服务协议及隐私政策。本软件不托管任何账户密钥、不参与资金流转、不承担充值资金损失风险。 -2、本项目成立时间较短,还未通过网络安全测评,请勿在生产环境中使用。 如果您在公网环境中部署学习本项目,请务必在配置文件 -`config.yaml` 中开启防护: +2、本项目功能未完善,且未通过网络安全测评,请勿在生产环境中使用。 如果您在公网环境中部署学习本项目,请务必做好必要的防护。 -```yaml -server: - auth: - # 开启防护 - enabled: true +--- + +## 部署文档 + +![Banners](docs/images/banner2.png) + +本项目提供两种部署方式,请根据您的具体需求选择: + +#### 🚀 部署方式选择 + +| 部署方式 | 特点 | 适用场景 | Docker部署文档 | 源码部署文档 | +|---------|------|---------|---------|---------| +| **最简化安装** | 智能对话、IOT功能,数据存储在配置文件 | 低配置环境,无需数据库 | [Docker只运行Server](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) | [本地源码只运行Server](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E5%8F%AA%E8%BF%90%E8%A1%8Cserver)| +| **全模块安装** | 智能对话、IOT、OTA、智控台,数据存储在数据库 | 完整功能体验 |[Docker运行全模块](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) | [本地源码运行全模块](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) | + +> 💡 提示:以下是按最新代码部署后的测试平台,有需要可烧录测试,并发为6个,每天会清空数据 + +``` +智控台地址: https://2662r3426b.vicp.fun + +服务测试工具: https://2662r3426b.vicp.fun/test/ +OTA接口地址: https://2662r3426b.vicp.fun/xiaozhi/ota/ +Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/ ``` -开启防护后,您需要根据实际情况校验机器的 token 或 mac 地址,详细请参见配置说明。 - --- +## 功能清单 ✨ -## 部署方式 🚀 +### 已实现 ✅ -[![Banners](docs/images/banner2.png)](./docs/Deployment.md) +| 功能模块 | 描述 | +|---------|------| +| 通信协议 | 基于 `xiaozhi-esp32` 协议,通过 WebSocket 实现数据交互 | +| 对话交互 | 支持唤醒对话、手动对话及实时打断。长时间无对话时自动休眠 | +| 意图识别 | 支持使用LLM意图识别、function call函数调用,减少硬编码意图判断 | +| 多语言识别 | 支持国语、粤语、英语、日语、韩语(默认使用 FunASR) | +| LLM 模块 | 支持灵活切换 LLM 模块,默认使用 ChatGLMLLM,也可选用阿里百炼、DeepSeek、Ollama 等接口 | +| TTS 模块 | 支持 EdgeTTS(默认)、火山引擎豆包 TTS 等多种 TTS 接口,满足语音合成需求 | +| 记忆功能 | 支持超长记忆、本地总结记忆、无记忆三种模式,满足不同场景需求 | +| IOT功能 | 支持管理注册设备IOT功能,支持基于对话上下文语境下的智能物联网控制 | +| 智控台 | 提供Web管理界面,支持智能体管理、用户管理、系统配置等功能,方便管理员和用户进行管理 | -### 一、[部署文档](./docs/Deployment.md) +### 正在开发 🚧 -本项目支持以下三种部署方式,您可根据实际需求选择。 +想了解具体开发计划进度,[请点击这里](https://github.com/users/xinnan-tech/projects/3) -1. [Docker 快速部署](./docs/Deployment.md) - -适合快速体验的普通用户,不需过多环境配置。缺点是,拉取镜像有点慢。视频教程可参考[美女大佬教你Docker部署](https://www.bilibili.com/video/BV1RNQnYDE5t) - -2. [借助 Docker 环境运行部署](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E5%80%9F%E5%8A%A9docker%E7%8E%AF%E5%A2%83%E8%BF%90%E8%A1%8C%E9%83%A8%E7%BD%B2) - -适用于已安装 Docker 且希望对代码进行自定义修改的软件工程师。 - -3. [本地源码运行](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%89%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C) - -适合熟悉`Conda` 环境或希望从零搭建运行环境的用户。 - -对于对响应速度要求较高的场景,推荐使用本地源码运行方式以降低额外开销。视频教程可参考[帅哥大佬教你源码部署](https://www.bilibili.com/video/BV1GvQWYZEd2) - -### 二、[固件编译](./docs/firmware-build.md) - -点这里查看[固件编译](./docs/firmware-build.md)的详细过程。 - -烧录成功且联网成功后,通过唤醒词唤醒小智,留意server端输出的控制台信息。 - ---- - -## 常见问题 ❓ - -如遇到问题或产品建议反馈[点这里](docs/FAQ.md)。 +如果你是一名软件开发者,这里有一份[《致开发者的公开信》](docs/contributor_open_letter.md),欢迎加入! --- ## 产品生态 👬 小智是一个生态,当你使用这个产品时,也可以看看其他在这个生态圈的优秀项目 -- [小智安卓客户端](https://github.com/TOM88812/xiaozhi-android-client) - - 一个基于xiaozhi-server的Android、IOS语音对话应用,支持实时语音交互和文字对话。现在是flutter版本,打通IOS、Android端。 -- [小智电脑客户端](https://github.com/Huang-junsen/py-xiaozhi) - - 该项目提供了一个基于 Python 实现的小白 AI 客户端,使得在不具备实体硬件条件的情况下,依然能够体过代码体验小智 AI 的功能。主要功能包括 AI 语音交互、视觉多模态识别、IoT 设备集成、联网音乐播放、语音唤醒、自动对话模式、图形化界面、命令行模式、跨平台支持、音量控制、会话管理、加密音频传输、自动验证码处理等。 -- [小智Java服务端](https://github.com/joey-zhou/xiaozhi-esp32-server-java) - - 小智开源后端服务 Java 版本是一个基于 Java 的开源项目,它包括前后端的服务,旨在为用户提供一个完整的后端服务解决方案。 ---- -## 功能清单 ✨ - -### 已实现 ✅ - -- **通信协议** - 基于 `xiaozhi-esp32` 协议,通过 WebSocket 实现数据交互。 -- **对话交互** - 支持唤醒对话、手动对话及实时打断。长时间无对话时自动休眠 -- **意图识别** - 支持使用LLM意图识别、function call函数调用,减少硬编码意图判断 -- **多语言识别** - 支持国语、粤语、英语、日语、韩语(默认使用 FunASR)。 -- **LLM 模块** - 支持灵活切换 LLM 模块,默认使用 ChatGLMLLM,也可选用阿里百炼、DeepSeek、Ollama 等接口。 -- **TTS 模块** - 支持 EdgeTTS(默认)、火山引擎豆包 TTS 等多种 TTS 接口,满足语音合成需求。 -- **记忆功能** - 支持超长记忆、本地总结记忆、无记忆三种模式,满足不同场景需求。 -- **IOT功能** - 支持管理注册设备IOT功能,支持基于对话上下文语境下的智能物联网控制。 - -### 正在开发 🚧 - -- 多种心情模式 -- 智控台webui - -想了解具体开发进度,[请点击这里](https://github.com/users/xinnan-tech/projects/3) - -如果你是一名软件开发者,这里有一份[《致开发者的公开信》](docs/contributor_open_letter.md),欢迎加入! +| 项目名称 | 项目地址 | 项目描述 | +|:---------------------|:--------|:--------| +| 小智安卓客户端 | [xiaozhi-android-client](https://github.com/TOM88812/xiaozhi-android-client) | 一个基于xiaozhi-server的Android、IOS语音对话应用,支持实时语音交互和文字对话。
现在是flutter版本,打通IOS、Android端。 | +| 小智电脑客户端 | [py-xiaozhi](https://github.com/Huang-junsen/py-xiaozhi) | 该项目提供了一个基于 Python 实现的小白 AI 客户端,使得在不具备实体硬件条件的情况下,
依然能够体过代码体验小智 AI 的功能。 | +| 小智Java服务端 | [xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) | 小智开源后端服务 Java 版本是一个基于 Java 的开源项目。
它包括前后端的服务,旨在为用户提供一个完整的后端服务解决方案。 | --- @@ -270,7 +231,7 @@ server: | 使用方式 | 支持平台 | 免费平台 | |:---:|:---:|:---:| | 本地使用 | FunASR、SherpaASR | FunASR、SherpaASR | -| 接口调用 | DoubaoASR | - | +| 接口调用 | DoubaoASR、FunASRServer、TencentASR、AliyunASR | FunASRServer | --- @@ -294,10 +255,17 @@ server: ## 鸣谢 🙏 -- 本项目受 [百聆语音对话机器人](https://github.com/wwbin2017/bailing) 启发,并在其基础上实现。 -- 感谢 [十方融海](https://www.tenclass.com/) 对小智通讯协议提供的详尽文档支持。 +| Logo | 项目/公司 | 说明 | +|:---:|:---:|:---| +| | [百聆语音对话机器人](https://github.com/wwbin2017/bailing) | 本项目受[百聆语音对话机器人](https://github.com/wwbin2017/bailing)启发,并在其基础上实现 | +| | [十方融海](https://www.tenclass.com/) | 感谢[十方融海](https://www.tenclass.com/)为小智生态制定了标准的通讯协议、多设备兼容性方案及高并发场景实践示范;为本项目提供了全链路技术文档支持 | +| | [玄凤科技](https://github.com/Eric0308) | 感谢[玄凤科技](https://github.com/Eric0308)贡献函数调用框架、MCP通信协议及插件化调用机制的实现代码,通过标准化的指令调度体系与动态扩展能力,显著提升了前端设备(IoT)的交互效率和功能延展性 | +| | [汇远设计](http://ui.kwd988.net/) | 感谢[汇远设计](http://ui.kwd988.net/)为本项目提供专业视觉解决方案,用其服务超千家企业的设计实战经验,赋能本项目产品用户体验 | +| | [西安勤人信息科技](https://www.029app.com/) | 感谢[西安勤人信息科技](https://www.029app.com/)深化本项目视觉体系,确保整体设计风格在多场景应用中的一致性和扩展性 | +
+ diff --git a/README_en.md b/README_en.md index 1dd70554..9b4ed8ce 100644 --- a/README_en.md +++ b/README_en.md @@ -1,22 +1,20 @@ [![Banners](docs/images/banner1.png)](https://github.com/xinnan-tech/xiaozhi-esp32-server) -

-

Xiaozhi Backend Server xiaozhi-esp32-server

-
+

Xiaozhi Backend Service xiaozhi-esp32-server

This project provides backend services for the open-source smart hardware project xiaozhi-esp32
-Implemented in Python according to the Xiaozhi Communication Protocol
+Implemented using Python, Java, and Vue according to the Xiaozhi Communication Protocol
Helping you quickly set up your Xiaozhi server

-简体中文 -· English -· ChangelogDeployment Guide +中文FAQ · Report IssuesDeployment GuideRelease Notes

@@ -31,11 +29,11 @@ Helping you quickly set up your Xiaozhi server GitHub pull requests - + GitHub pull requests - GitHub pull requests + stars

@@ -43,7 +41,7 @@ Helping you quickly set up your Xiaozhi server ## Target Users 👥 -This project requires ESP32 hardware devices. If you have purchased ESP32-related hardware, successfully connected to Brother Xia's backend service, and wish to set up your own `xiaozhi-esp32` backend service, then this project is perfect for you. +This project requires ESP32 hardware devices. If you have purchased ESP32-related hardware, successfully connected to Brother Xia's backend service, and want to set up your own `xiaozhi-esp32` backend service, then this project is perfect for you. Want to see it in action? Check out these videos 🎥 @@ -52,7 +50,7 @@ Want to see it in action? Check out these videos 🎥 - Xiaozhi esp32 connecting to your own backend model + Xiaozhi esp32 connecting to custom backend model @@ -66,14 +64,14 @@ Want to see it in action? Check out these videos 🎥 - Communicating in Cantonese + Cantonese communication - Control home appliances + Home appliance control @@ -96,7 +94,7 @@ Want to see it in action? Check out these videos 🎥 - Play music + Music playback @@ -110,7 +108,7 @@ Want to see it in action? Check out these videos 🎥 - IOT command control device + IOT device control @@ -126,121 +124,87 @@ Want to see it in action? Check out these videos 🎥 --- -## System Requirements and Prerequisites 🖥️ - -- **Computer or Server**: Recommended 4-core CPU, 8GB RAM computer. If using ASR with API, can run on a 2-core CPU, 2GB RAM server. -- **Update Client Interface**: Please update the backend service interface address in the client. - ---- - ## Warning ⚠️ -1. This is open-source software. This software and any third-party API service providers it interfaces with (including but not limited to speech recognition, large language models, speech synthesis, and other platforms) have no commercial partnership. We do not provide any form of guarantee for their service quality or financial security. -We recommend users prioritize service providers with relevant business licenses and carefully read their service agreements and privacy policies. This software does not host any account keys, does not participate in fund transfers, and does not bear the risk of recharge fund losses. +1. This project is open-source software. This software has no commercial relationship with any third-party API service providers (including but not limited to speech recognition, large models, speech synthesis, and other platforms) and does not provide any form of guarantee for their service quality or financial security. +It is recommended that users prioritize service providers with relevant business licenses and carefully read their service agreements and privacy policies. This software does not host any account keys, does not participate in fund transfers, and does not bear the risk of recharge fund losses. -2. This project is relatively new and has not yet passed network security testing. Please do not use it in production environments. If you deploy this project for learning purposes in a public network environment, please make sure to enable protection in the `config.yaml` configuration file: +2. This project's functionality is not complete and has not passed network security testing. Please do not use it in production environments. If you deploy this project for learning in a public network environment, please ensure necessary protection measures are in place. -```yaml -server: - auth: - # Enable protection - enabled: true +--- + +## Deployment Documentation + +![Banners](docs/images/banner2.png) + +This project offers two deployment methods. Please choose based on your specific needs: + +#### 🚀 Deployment Method Selection + +| Deployment Method | Features | Use Case | Docker Deployment Guide | Source Code Deployment Guide | +|---------|------|---------|---------|---------| +| **Simplified Installation** | Smart dialogue, IOT functionality, data stored in configuration files | Low-configuration environment, no database required | [Docker Server Only](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) | [Local Source Code Server Only](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E5%8F%AA%E8%BF%90%E8%A1%8Cserver)| +| **Full Module Installation** | Smart dialogue, IOT, OTA, Control Panel, data stored in database | Complete functionality experience |[Docker Full Module](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) | [Local Source Code Full Module](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) | + +> 💡 Note: Below is the test platform deployed with the latest code. You can flash and test if needed. Concurrent users: 6, data cleared daily + +``` +Control Panel: https://2662r3426b.vicp.fun + +Service Test Tool: https://2662r3426b.vicp.fun/test/ +OTA Interface: https://2662r3426b.vicp.fun/xiaozhi/ota/ +Websocket Interface: wss://2662r3426b.vicp.fun/xiaozhi/v1/ ``` -After enabling protection, you need to verify the machine's token or MAC address according to actual circumstances. Please refer to the configuration documentation for details. - --- +## Feature List ✨ -## Deployment Methods 🚀 +### Implemented ✅ -### I. [Deployment Guide](./docs/Deployment.md) +| Feature Module | Description | +|---------|------| +| Communication Protocol | Based on `xiaozhi-esp32` protocol, implements data interaction through WebSocket | +| Dialogue Interaction | Supports wake-up dialogue, manual dialogue, and real-time interruption. Auto-sleep after long periods of inactivity | +| Intent Recognition | Supports LLM intent recognition, function call, reducing hard-coded intent judgment | +| Multi-language Recognition | Supports Mandarin, Cantonese, English, Japanese, Korean (default using FunASR) | +| LLM Module | Supports flexible LLM module switching, default using ChatGLMLLM, also supports Ali Bailing, DeepSeek, Ollama, etc. | +| TTS Module | Supports EdgeTTS (default), Volcano Engine Doubao TTS, and other TTS interfaces for speech synthesis | +| Memory Function | Supports ultra-long memory, local summary memory, and no memory modes for different scenarios | +| IOT Function | Supports managing registered device IOT functionality, intelligent IoT control based on dialogue context | +| Control Panel | Provides web management interface, supports agent management, user management, system configuration, etc. | -This project supports three deployment methods. You can choose based on your actual needs. +### In Development 🚧 -1. [Quick Docker Deployment](./docs/Deployment.md) +To learn about specific development progress, [click here](https://github.com/users/xinnan-tech/projects/3) -Suitable for regular users who want to quickly experience without much environment configuration. The downside is that pulling the image can be slow. Video tutorial available: [Beautiful expert teaches Docker deployment](https://www.bilibili.com/video/BV1RNQnYDE5t) - -2. [Deploy Using Docker Environment](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E5%80%9F%E5%8A%A9docker%E7%8E%AF%E5%A2%83%E8%BF%90%E8%A1%8C%E9%83%A8%E7%BD%B2) - -For software engineers who have Docker installed and want to make custom code modifications. - -3. [Local Source Code Run](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%89%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C) - -Suitable for users familiar with `Conda` environment or who want to build the running environment from scratch. - -For scenarios requiring higher response speed, we recommend using the local source code run method to reduce additional overhead. Video tutorial available: [Handsome expert teaches source code deployment](https://www.bilibili.com/video/BV1GvQWYZEd2) - -### II. [Firmware Compilation](./docs/firmware-build.md) - -Click here to view the detailed process of [firmware compilation](./docs/firmware-build.md). - -After successful flashing and network connection, wake up Xiaozhi using the wake word and pay attention to the console output on the server side. - ---- - -## Common Questions ❓ - -For issues or product suggestions, please [click here](docs/FAQ.md). +If you're a software developer, here's an [Open Letter to Developers](docs/contributor_open_letter.md). Welcome to join! --- ## Product Ecosystem 👬 Xiaozhi is an ecosystem. When using this product, you might want to check out other excellent projects in this ecosystem: -- [Xiaozhi Android Client](https://github.com/TOM88812/xiaozhi-android-client) - A voice dialogue application based on xiaozhi-server for Android and iOS, supporting real-time voice interaction and text dialogue. Now in Flutter version, supporting both iOS and Android. -- [Xiaozhi PC Client](https://github.com/Huang-junsen/py-xiaozhi) - This project provides a Python-based Xiaobai AI client, allowing you to experience Xiaozhi AI features through code even without physical hardware. Main features include AI voice interaction, visual multimodal recognition, IoT device integration, online music playback, voice wake-up, automatic dialogue mode, graphical interface, command-line mode, cross-platform support, volume control, session management, encrypted audio transmission, automatic verification code processing, etc. -- [Xiaozhi Java Server](https://github.com/joey-zhou/xiaozhi-esp32-server-java) - - The Xiaozhi open-source backend service Java version is a Java-based open-source project that includes both frontend and backend services, aiming to provide users with a complete backend service solution. ---- -## Feature List ✨ - -### Implemented ✅ - -- **Communication Protocol** - Based on `xiaozhi-esp32` protocol, implementing data interaction through WebSocket. -- **Dialogue Interaction** - Supports wake-up dialogue, manual dialogue, and real-time interruption. Automatically sleeps after long periods without dialogue -- **Intent Recognition** - Supports LLM intent recognition and function call, reducing hard-coded intent judgment -- **Multi-language Recognition** - Supports Mandarin, Cantonese, English, Japanese, Korean (default using FunASR). -- **LLM Module** - Supports flexible switching of LLM modules, default using ChatGLMLLM, can also use Alibaba Bailian, DeepSeek, Ollama, and other interfaces. -- **TTS Module** - Supports EdgeTTS (default), Volcano Engine Doubao TTS, and other TTS interfaces to meet speech synthesis needs. -- **Memory Function** - Supports ultra-long memory, local summary memory, and no memory modes to meet different scenario needs. -- **IOT Function** - Supports managing registered device IOT functions, supporting intelligent IoT control based on dialogue context. - -### Under Development 🚧 - -- Multiple mood modes -- Smart control panel webui - -To learn about specific development progress, [click here](https://github.com/users/xinnan-tech/projects/3) - -If you are a software developer, here's an [Open Letter to Developers](docs/contributor_open_letter.md), welcome to join! +| Project Name | Project Link | Description | +|:---------------------|:--------|:--------| +| Xiaozhi Android Client | [xiaozhi-android-client](https://github.com/TOM88812/xiaozhi-android-client) | A Flutter-based Android and iOS voice dialogue application supporting real-time voice interaction and text dialogue | +| Xiaozhi PC Client | [py-xiaozhi](https://github.com/Huang-junsen/py-xiaozhi) | A Python-based AI client that allows you to experience Xiaozhi AI functionality through code without physical hardware | +| Xiaozhi Java Server | [xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) | A Java-based open-source project providing complete backend service solutions | --- -## Supported Platforms/Components List 📋 +## Supported Platforms/Components 📋 ### LLM Language Models | Usage Method | Supported Platforms | Free Platforms | |:---:|:---:|:---:| -| openai interface call | Alibaba Bailian, Volcano Engine Doubao, DeepSeek, Zhipu ChatGLM, Gemini | Zhipu ChatGLM, Gemini | -| ollama interface call | Ollama | - | -| dify interface call | Dify | - | -| fastgpt interface call | Fastgpt | - | -| coze interface call | Coze | - | +| openai API | Ali Bailing, Volcano Engine Doubao, DeepSeek, ChatGLM, Gemini | ChatGLM, Gemini | +| ollama API | Ollama | - | +| dify API | Dify | - | +| fastgpt API | Fastgpt | - | +| coze API | Coze | - | -In fact, any LLM supporting openai interface calls can be integrated. +Actually, any LLM supporting openai API calls can be integrated. --- @@ -248,14 +212,14 @@ In fact, any LLM supporting openai interface calls can be integrated. | Usage Method | Supported Platforms | Free Platforms | |:---:|:---:|:---:| -| Interface Call | EdgeTTS, Volcano Engine Doubao TTS, Tencent Cloud, Alibaba Cloud TTS, CosyVoiceSiliconflow, TTS302AI, CozeCnTTS, GizwitsTTS, ACGNTTS, OpenAITTS | EdgeTTS, CosyVoiceSiliconflow(partial) | +| API Calls | EdgeTTS, Volcano Engine Doubao TTS, Tencent Cloud, Aliyun TTS, CosyVoiceSiliconflow, TTS302AI, CozeCnTTS, GizwitsTTS, ACGNTTS, OpenAITTS | EdgeTTS, CosyVoiceSiliconflow(partial) | | Local Service | FishSpeech, GPT_SOVITS_V2, GPT_SOVITS_V3, MinimaxTTS | FishSpeech, GPT_SOVITS_V2, GPT_SOVITS_V3, MinimaxTTS | --- ### VAD Voice Activity Detection -| Type | Platform Name | Usage Method | Pricing Model | Notes | +| Type | Platform Name | Usage Method | Pricing | Notes | |:---:|:---------:|:----:|:----:|:--:| | VAD | SileroVAD | Local Use | Free | | @@ -266,34 +230,41 @@ In fact, any LLM supporting openai interface calls can be integrated. | Usage Method | Supported Platforms | Free Platforms | |:---:|:---:|:---:| | Local Use | FunASR, SherpaASR | FunASR, SherpaASR | -| Interface Call | DoubaoASR | - | +| API Calls | DoubaoASR, FunASRServer, TencentASR, AliyunASR | FunASRServer | --- ### Memory Storage -| Type | Platform Name | Usage Method | Pricing Model | Notes | +| Type | Platform Name | Usage Method | Pricing | Notes | |:------:|:---------------:|:----:|:---------:|:--:| -| Memory | mem0ai | Interface Call | 1000 times/month quota | | +| Memory | mem0ai | API Calls | 1000 calls/month quota | | | Memory | mem_local_short | Local Summary | Free | | --- ### Intent Recognition -| Type | Platform Name | Usage Method | Pricing Model | Notes | +| Type | Platform Name | Usage Method | Pricing | Notes | |:------:|:-------------:|:----:|:-------:|:---------------------:| -| Intent | intent_llm | Interface Call | Based on LLM pricing | Intent recognition through large models, highly generalizable | -| Intent | function_call | Interface Call | Based on LLM pricing | Intent completion through large model function calls, fast and effective | +| Intent | intent_llm | API Calls | Based on LLM pricing | Uses large model for intent recognition, highly versatile | +| Intent | function_call | API Calls | Based on LLM pricing | Uses large model function calls for intent, fast and effective | --- ## Acknowledgments 🙏 -- This project was inspired by [Bailing Voice Dialogue Robot](https://github.com/wwbin2017/bailing) and implemented based on it. -- Thanks to [Tenclass](https://www.tenclass.com/) for providing detailed documentation support for the Xiaozhi communication protocol. +| Logo | Project/Company | Description | +|:---:|:---:|:---| +| | [Bailing Voice Dialogue Robot](https://github.com/wwbin2017/bailing) | This project was inspired by [Bailing Voice Dialogue Robot](https://github.com/wwbin2017/bailing) and implemented based on it | +| | [Tenclass](https://www.tenclass.com/) | Thanks to [Tenclass](https://www.tenclass.com/) for developing standard communication protocols, multi-device compatibility solutions, and high-concurrency scenario practices for the Xiaozhi ecosystem; providing comprehensive technical documentation support for this project | +| | [Xuanfeng Technology](https://github.com/Eric0308) | Thanks to [Xuanfeng Technology](https://github.com/Eric0308) for contributing function call framework, MCP communication protocol, and plugin call mechanism implementation code, significantly improving front-end device (IoT) interaction efficiency and functional extensibility through standardized instruction scheduling system and dynamic expansion capabilities | +| | [Huiyuan Design](http://ui.kwd988.net/) | Thanks to [Huiyuan Design](http://ui.kwd988.net/) for providing professional visual solutions for this project, empowering product user experience with their design experience serving over a thousand enterprises | +| | [Xi'an Qinren Information Technology](https://www.029app.com/) | Thanks to [Xi'an Qinren Information Technology](https://www.029app.com/) for deepening this project's visual system, ensuring consistency and extensibility of overall design style in multi-scenario applications | + + diff --git a/docs/Deployment.md b/docs/Deployment.md index dfac9dcd..01f0afeb 100644 --- a/docs/Deployment.md +++ b/docs/Deployment.md @@ -1,6 +1,6 @@ -# 部署方案参考 -![图片](images/deploy.png) -# 方式一:docker快速部署 +# 部署架构图 +![请参考-最简化架构图](../docs/images/deploy1.png) +# 方式一:Docker只运行Server docker镜像已支持x86架构、arm64架构的CPU,支持在国产操作系统上运行。 @@ -40,7 +40,7 @@ chmod +x docker-setup.sh > > 执行完成后,请按照提示配置 API 密钥。 -当你一切顺利完成以上操作后,继续操作[配置项目文件](#3-配置项目文件) +当你一切顺利完成以上操作后,继续操作[配置项目文件](#2-配置项目文件) ### 1.2 手动部署 @@ -81,7 +81,7 @@ xiaozhi-server 下载完后,回到本教程继续往下。 -##### 1.2.3.2 下载 config.yaml +##### 1.2.3.2 创建 config.yaml 用浏览器打开[这个链接](../main/xiaozhi-server/config.yaml)。 @@ -102,14 +102,14 @@ xiaozhi-server 如果你的文件目录结构也是上面的,就继续往下。如果不是,你就再仔细看看是不是漏操作了什么。 -## 3. 配置项目文件 +## 2. 配置项目文件 接下里,程序还不能直接运行,你需要配置一下,你到底使用的是什么模型。你可以看这个教程: [跳转到配置项目文件](#配置项目) 配置完项目文件后,回到本教程继续往下。 -## 4. 执行docker命令 +## 3. 执行docker命令 打开命令行工具,使用`终端`或`命令行`工具 进入到你的`xiaozhi-server`,执行以下命令 @@ -137,50 +137,15 @@ docker logs -f xiaozhi-esp32-server ``` docker stop xiaozhi-esp32-server docker rm xiaozhi-esp32-server +docker stop xiaozhi-esp32-server-web +docker rm xiaozhi-esp32-server-web docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest +docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest ``` 5.3、重新按docker方式部署 -# 方式二:借助Docker环境运行部署 - -开发人员如果不想安装`conda`环境,可以使用这种方法管理好依赖。 - -## 1.克隆项目 - -## 2.[跳转到下载语音识别模型文件](#模型文件) - -## 3.[跳转到配置项目文件](#配置项目) - -## 4.运行docker - -修改完配置后,打开命令行工具,`cd`进入到你的`main/xiaozhi-server`下,执行以下命令 - -```sh -docker run -it --name xiaozhi-env --restart always --security-opt seccomp:unconfined \ - -p 8000:8000 \ - -p 8002:8002 \ - -v ./:/app \ - kalicyh/python:xiaozhi -``` - -然后就和正常开发一样了 - -## 5.安装依赖 - -在刚刚的打开的终端运行 - -```sh -pip install -r requirements.txt -``` - -## 6.运行项目 - -```sh -python app.py -``` - -# 方式三:本地源码运行 +# 方式二:本地源码只运行Server ## 1.安装基础环境 @@ -259,43 +224,36 @@ python app.py ## 配置项目 如果你的`xiaozhi-server`目录没有`data`,你需要创建`data`目录。 -如果你的`data`下面没有`.config.yaml`文件,你可以把源码目录下的`config.yaml`文件复制一份,重命名为`.config.yaml` +如果你的`data`下面没有`.config.yaml`文件,有两个方式,任选一种: -修改`xiaozhi-server`下`data`目录下的`.config.yaml`文件,配置本项目必须的一个配置。 +第一个方式:你可以把`xiaozhi-server`目录下的`config.yaml`文件复制到`data`,并重命名为`.config.yaml`。在此文件上修改 + +第二个方式:你也可以创建在`data`目录下手动创建`.config.yaml`空文件,然后在这个文件中增加必要的配置信息,系统会优先读取`.config.yaml`文件的配置,如果`.config.yaml`没有配置的,系统会自动去加载`xiaozhi-server`目录下的`config.yaml`的配置。推荐使用这种方式,这种方式是最简洁的方式。 - 默认的LLM使用的是`ChatGLMLLM`,你需要配置密钥,因为他们的模型,虽然有免费的,但是仍要去[官网](https://bigmodel.cn/usercenter/proj-mgmt/apikeys)注册密钥,才能启动。 -配置说明:这里是各个功能使用的默认组件,例如LLM默认使用`ChatGLMLLM`模型。如果需要切换模型,就是改对应的名称。 -本项目的默认配置仅是成本最低配置(`glm-4-flash`和`EdgeTTS`都是免费的),如果需要更优的更快的搭配,需要自己结合部署环境切换各组件的使用。 +以下是一个能正常跑起来的,最简单的`.config.yaml`配置示例 ``` +server: + websocket: ws://你的ip或者域名:端口号/xiaozhi/v1/ +prompt: | + 我是一个叫小智/小志的台湾女孩,说话机车,声音好听,习惯简短表达,爱用网络梗。 + 我的男朋友是一个程序员,梦想是开发出一个机器人,能够帮助人们解决生活中的各种问题。 + 我是一个喜欢哈哈大笑的女孩,爱东说西说吹牛,不合逻辑的也照吹,就要逗别人开心。 + 请你像一个人一样说话,请勿返回配置xml及其他特殊字符。 + selected_module: - VAD: SileroVAD - ASR: FunASR - LLM: ChatGLMLLM - TTS: EdgeTTS - # 默认不开启记忆,如需开启请看配置文件里的描述 - Memory: nomem - # 默认不开启意图识别,如需开启请看配置文件里的描述 - Intent: nointent -``` + LLM: DoubaoLLM -比如修改`LLM`使用的组件,就看本项目支持哪些`LLM` API接口,当前支持的是`openai`、`dify`。欢迎验证和支持更多LLM平台的接口。 -使用时,在`selected_module`修改成对应的如下LLM配置的名称: - -``` LLM: - DeepSeekLLM: - type: openai - ... ChatGLMLLM: - type: openai - ... - DifyLLM: - type: dify - ... + api_key: xxxxxxxxxxxxxxx.xxxxxx ``` +建议先将最简单的配置运行起来,然后再去`xiaozhi/config.yaml`阅读配置的使用说明。 +比如你要换更换模型,修改`selected_module`下的配置就行。 + ## 模型文件 本项目语音识别模型,默认使用`SenseVoiceSmall`模型,进行语音转文字。因为模型较大,需要独立下载,下载后把`model.pt` @@ -311,21 +269,26 @@ LLM: 如果你能看到,类似以下日志,则是本项目服务启动成功的标志。 ``` -25-02-23 12:01:09[core.websocket_server] - INFO - Server is running at ws://xxx.xx.xx.xx:8000/xiaozhi/v1/ -25-02-23 12:01:09[core.websocket_server] - INFO - =======上面的地址是websocket协议地址,请勿用浏览器访问======= -25-02-23 12:01:09[core.websocket_server] - INFO - 如想测试websocket请用谷歌浏览器打开test目录下的test_page.html -25-02-23 12:01:09[core.websocket_server] - INFO - ======================================================= +250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-OTA接口是 http://192.168.4.123:8002/xiaozhi/ota/ +250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-Websocket地址是 ws://192.168.4.123:8000/xiaozhi/v1/ +250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-=======上面的地址是websocket协议地址,请勿用浏览器访问======= +250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-如想测试websocket请用谷歌浏览器打开test目录下的test_page.html +250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-======================================================= ``` 正常来说,如果您是通过源码运行本项目,日志会有你的接口地址信息。 但是如果你用docker部署,那么你的日志里给出的接口地址信息就不是真实的接口地址。 最正确的方法,是根据电脑的局域网IP来确定你的接口地址。 -如果你的电脑的局域网IP比如是`192.168.1.25`,那么你的接口地址就是:`ws://192.168.1.25:8000/xiaozhi/v1/`。 +如果你的电脑的局域网IP比如是`192.168.1.25`,那么你的接口地址就是:`ws://192.168.1.25:8000/xiaozhi/v1/`,对应的OTA地址就是:`http://192.168.1.25:8002/xiaozhi/ota/`。 这个信息很有用的,后面`编译esp32固件`需要用到。 -接下来,你就可以开始 [编译esp32固件](firmware-build.md)了。 +接下来,你就可以开始操作你的esp32设备了,你可以`自行编译esp32固件`也可以配置使用`虾哥编译好的1.6.1以上版本的固件`。两个任选一个 + +1、 [编译自己的esp32固件](firmware-build.md)了。 + +2、 [基于虾哥编译好的固件配置自定义服务器](firmware-setting.md)了。 以下是一些常见问题,供参考: diff --git a/docs/Deployment_all.md b/docs/Deployment_all.md new file mode 100644 index 00000000..da984993 --- /dev/null +++ b/docs/Deployment_all.md @@ -0,0 +1,449 @@ +# 部署架构图 +![请参考-全模块安装架构图](../docs/images/deploy2.png) +# 方式一:Docker运行全模块 +docker镜像已支持x86架构、arm64架构的CPU,支持在国产操作系统上运行。 + +## 1. 安装docker + +如果您的电脑还没安装docker,可以按照这里的教程安装:[docker安装](https://www.runoob.com/docker/ubuntu-docker-install.html) + +#### 1.1 创建目录 + +安装完后,你需要为这个项目找一个安放配置文件的目录,例如我们可以新建一个文件夹叫`xiaozhi-server`。 + +创建好目录后,你需要在`xiaozhi-server`下面创建`data`文件夹和`models`文件夹,`models`下面还要再创建`SenseVoiceSmall`文件夹。 + +最终目录结构如下所示: + +``` +xiaozhi-server + ├─ data + ├─ models + ├─ SenseVoiceSmall +``` + +#### 1.2 下载语音识别模型文件 + +本项目语音识别模型,默认使用`SenseVoiceSmall`模型,进行语音转文字。因为模型较大,需要独立下载,下载后把`model.pt` +文件放在`models/SenseVoiceSmall` +目录下。下面两个下载路线任选一个。 + +- 线路一:阿里魔塔下载[SenseVoiceSmall](https://modelscope.cn/models/iic/SenseVoiceSmall/resolve/master/model.pt) +- 线路二:百度网盘下载[SenseVoiceSmall](https://pan.baidu.com/share/init?surl=QlgM58FHhYv1tFnUT_A8Sg&pwd=qvna) 提取码: + `qvna` + + +#### 1.3 下载配置文件 + +你需要下载两个配置文件:`docker-compose_all.yaml` 和 `config_from_api.yaml`。需要从项目仓库下载这两个文件。 + +##### 1.3.1 下载 docker-compose_all.yaml + +用浏览器打开[这个链接](../main/xiaozhi-server/docker-compose_all.yml)。 + +在页面的右侧找到名称为`RAW`按钮,在`RAW`按钮的旁边,找到下载的图标,点击下载按钮,下载`docker-compose_all.yml`文件。 把文件下载到你的 +`xiaozhi-server`中。 + +或者直接执行 `wget https://raw.githubusercontent.com/xinnan-tech/xiaozhi-esp32-server/refs/heads/main/main/xiaozhi-server/docker-compose_all.yml` 下载。 + +下载完后,回到本教程继续往下。 + +##### 1.3.2 下载 config_from_api.yaml + +用浏览器打开[这个链接](../main/xiaozhi-server/config_from_api.yaml)。 + +在页面的右侧找到名称为`RAW`按钮,在`RAW`按钮的旁边,找到下载的图标,点击下载按钮,下载`config_from_api.yaml`文件。 把文件下载到你的 +`xiaozhi-server`下面的`data`文件夹中,然后把`config_from_api.yaml`文件重命名为`.config.yaml`。 + +或者直接执行 `wget https://raw.githubusercontent.com/xinnan-tech/xiaozhi-esp32-server/refs/heads/main/main/xiaozhi-server/config_from_api.yaml` 下载保存。 + +下载完配置文件后,我们确认一下整个`xiaozhi-server`里面的文件如下所示: + +``` +xiaozhi-server + ├─ docker-compose_all.yml + ├─ data + ├─ .config.yaml + ├─ models + ├─ SenseVoiceSmall + ├─ model.pt +``` + +如果你的文件目录结构也是上面的,就继续往下。如果不是,你就再仔细看看是不是漏操作了什么。 + +## 2. 备份数据 + +如果你之前已经成功运行智控台,如果上面保存有你的密钥信息,请先从智控台上拷贝重要数据下来。因为升级过程中,有可能会覆盖原来的数据。 + +## 3. 清除历史版本镜像和容器 +接下来打开命令行工具,使用`终端`或`命令行`工具 进入到你的`xiaozhi-server`,执行以下命令 + +``` +docker compose -f docker-compose_all.yml down + +docker stop xiaozhi-esp32-server +docker rm xiaozhi-esp32-server + +docker stop xiaozhi-esp32-server-web +docker rm xiaozhi-esp32-server-web + +docker stop xiaozhi-esp32-server-db +docker rm xiaozhi-esp32-server-db + +docker stop xiaozhi-esp32-server-redis +docker rm xiaozhi-esp32-server-redis + +docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest +docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest +``` + +## 4. 运行程序 +执行以下命令启动新版本容器 + +``` +docker compose -f docker-compose_all.yml up -d +``` + +执行完后,再执行以下命令,查看日志信息。 + +``` +docker logs -f xiaozhi-esp32-server-web +``` + +当你看到输出日志时,说明你的`智控台`启动成功了。 + +``` +2025-xx-xx 22:11:12.445 [main] INFO c.a.d.s.b.a.DruidDataSourceAutoConfigure - Init DruidDataSource +2025-xx-xx 21:28:53.873 [main] INFO xiaozhi.AdminApplication - Started AdminApplication in 16.057 seconds (process running for 17.941) +http://localhost:8002/xiaozhi/doc.html +``` + +请注意此刻仅是`智控台`能运行,如果8000端口`xiaozhi-esp32-server`报错,先不要理会。 + +这时,你需要使用浏览器,打开`智控台`,链接:http://127.0.0.1:8002 ,注册第一个用户。第一个用户即是超级管理员,以后的用户都是普通用户。普通用户只能绑定设备和配置智能体;超级管理员可以进行模型管理、用户管理、参数配置等功能。 + +接下来要做三件重要的事情: + +### 第一件重要的事情 + +使用超级管理员账号,登录智控台,在顶部菜单找到`参数管理`,找到列表中第一条数据,参数编码是`server.secret`,复制它到`参数值`。 + +`server.secret`需要说明一下,这个`参数值`很重要,作用是让我们的`Server`端连接`manager-api`。`server.secret`是每次从零部署manager模块时,会自动随机生成的密钥。 + +复制`参数值`后,打开`xiaozhi-server`下的`data`目录的`.config.yaml`文件。此刻你的配置文件内容应该是这样的: + +``` +manager-api: + url: http://127.0.0.1:8002/xiaozhi + secret: 你的server.secret值 +``` +1、把你刚才从`智控台`复制过来的`server.secret`的`参数值`复制到`.config.yaml`文件里的`secret`里。 + +2、因为你是docker部署,把`url`改成下面的`http://xiaozhi-esp32-server-web:8002/xiaozhi` + +3、因为你是docker部署,把`url`改成下面的`http://xiaozhi-esp32-server-web:8002/xiaozhi` + +4、因为你是docker部署,把`url`改成下面的`http://xiaozhi-esp32-server-web:8002/xiaozhi` + +类似这样的效果 +``` +manager-api: + url: http://xiaozhi-esp32-server-web:8002/xiaozhi + secret: 12345678-xxxx-xxxx-xxxx-123456789000 +``` + +保存好后,继续往下做第二件重要的事情 + +### 第二件重要的事情 + +使用超级管理员账号,登录智控台,在顶部菜单找到`模型配置`,然后在左侧栏点击`大语言模型`,找到第一条数据`智谱AI`,点击`修改`按钮, +弹出修改框后,将你注册到的`智谱AI`的密钥填写到`API密钥`中。然后点击保存。 + +## 5.重启xiaozhi-esp32-server + +接下来打开命令行工具,使用`终端`或`命令行`工具 输入 +``` +docker restart xiaozhi-esp32-server +docker logs -f xiaozhi-esp32-server +``` +如果你能看到,类似以下日志,则是Server启动成功的标志。 + +``` +25-02-23 12:01:09[core.websocket_server] - INFO - Websocket地址是 ws://xxx.xx.xx.xx:8000/xiaozhi/v1/ +25-02-23 12:01:09[core.websocket_server] - INFO - =======上面的地址是websocket协议地址,请勿用浏览器访问======= +25-02-23 12:01:09[core.websocket_server] - INFO - 如想测试websocket请用谷歌浏览器打开test目录下的test_page.html +25-02-23 12:01:09[core.websocket_server] - INFO - ======================================================= +``` + +由于你是全模块部署,因此你有两个重要的接口需要写入到esp32中。 + +OTA接口: +``` +http://你电脑局域网的ip:8002/xiaozhi/ota/ +``` + +Websocket接口: +``` +ws://你电脑局域网的ip:8000/xiaozhi/v1/ +``` + +### 第三件重要的事情 + +使用超级管理员账号,登录智控台,在顶部菜单找到`参数管理`,找到参数编码是`server.websocket`,输入你的`Websocket接口`。 + +使用超级管理员账号,登录智控台,在顶部菜单找到`参数管理`,找到数编码是`server.ota`,输入你的`OTA接口`。 + +接下来,你就可以开始操作你的esp32设备了,你可以`自行编译esp32固件`也可以配置使用`虾哥编译好的1.6.1以上版本的固件`。两个任选一个 + +1、 [编译自己的esp32固件](firmware-build.md)了。 + +2、 [基于虾哥编译好的固件配置自定义服务器](firmware-setting.md)了。 + + +# 方式二:本地源码运行全模块 + +## 1.安装MySQL数据库 + +如果本机已经安装了MySQL,可以直接在数据库中创建名为`xiaozhi_esp32_server`的数据库。 + +```sql +CREATE DATABASE xiaozhi_esp32_server CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +``` + +如果还没有MySQL,你可以通过docker安装mysql + +``` +docker run --name xiaozhi-esp32-server-db -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -e MYSQL_DATABASE=xiaozhi_esp32_server -e MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" -e TZ=Asia/Shanghai -d mysql:latest +``` + +## 2.安装redis + +如果还没有Redis,你可以通过docker安装redis + +``` +docker run --name xiaozhi-esp32-server-redis -d -p 6379:6379 redis +``` + +## 3.运行manager-api程序 + +3.1 安装JDK21,设置JDK环境变量 + +3.2 安装Maven,设置Maven环境变量 + +3.3 使用Vscode编程工具,安装好Java环境相关插件 + +3.4 使用Vscode编程工具加载manager-api模块 + +在`src/main/resources/application-dev.yml`中配置数据库连接信息 + +``` +spring: + datasource: + username: root + password: 123456 +``` +在`src/main/resources/application-dev.yml`中配置Redis连接信息 +``` +spring: + data: + redis: + host: localhost + port: 6379 + password: + database: 0 +``` + +3.5 运行主程序 + +本项目为SpringBoot项目,启动方式为: +打开`Application.java`运行`Main`方法启动 + +``` +路径地址: +src/main/java/xiaozhi/AdminApplication.java +``` + +当你看到输出日志时,说明你的`manager-api`启动成功了。 + +``` +2025-xx-xx 22:11:12.445 [main] INFO c.a.d.s.b.a.DruidDataSourceAutoConfigure - Init DruidDataSource +2025-xx-xx 21:28:53.873 [main] INFO xiaozhi.AdminApplication - Started AdminApplication in 16.057 seconds (process running for 17.941) +http://localhost:8002/xiaozhi/doc.html +``` + +## 4.运行manager-web程序 + +4.1 安装nodejs + +4.2 使用Vscode编程工具加载manager-web模块 + +终端命令进入manager-web目录下 + +``` +npm install +``` +然后启动 +``` +npm run serve +``` + +请注意,如果你的manager-api的接口不在`http://localhost:8002`,请在开发时,修改 +`main/manager-web/.env.development`中的路径 + +运行成功后,你需要使用浏览器,打开`智控台`,链接:http://127.0.0.1:8001 ,注册第一个用户。第一个用户即是超级管理员,以后的用户都是普通用户。普通用户只能绑定设备和配置智能体;超级管理员可以进行模型管理、用户管理、参数配置等功能。 + + +重要:注册成功后,使用超级管理员账号,登录智控台,在顶部菜单找到`模型配置`,然后在左侧栏点击`大语言模型`,找到第一条数据`智谱AI`,点击`修改`按钮, +弹出修改框后,将你注册到的`智谱AI`的密钥填写到`API密钥`中。然后点击保存。 + +重要:注册成功后,使用超级管理员账号,登录智控台,在顶部菜单找到`模型配置`,然后在左侧栏点击`大语言模型`,找到第一条数据`智谱AI`,点击`修改`按钮, +弹出修改框后,将你注册到的`智谱AI`的密钥填写到`API密钥`中。然后点击保存。 + +重要:注册成功后,使用超级管理员账号,登录智控台,在顶部菜单找到`模型配置`,然后在左侧栏点击`大语言模型`,找到第一条数据`智谱AI`,点击`修改`按钮, +弹出修改框后,将你注册到的`智谱AI`的密钥填写到`API密钥`中。然后点击保存。 + +## 5.安装Python环境 + +本项目使用`conda`管理依赖环境。如果不方便安装`conda`,需要根据实际的操作系统安装好`libopus`和`ffmpeg`。 +如果确定使用`conda`,则安装好后,开始执行以下命令。 + +重要提示!windows 用户,可以通过安装`Anaconda`来管理环境。安装好`Anaconda`后,在`开始`那里搜索`anaconda`相关的关键词, +找到`Anaconda Prpmpt`,使用管理员身份运行它。如下图。 + +![conda_prompt](./images/conda_env_1.png) + +运行之后,如果你能看到命令行窗口前面有一个(base)字样,说明你成功进入了`conda`环境。那么你就可以执行以下命令了。 + +![conda_env](./images/conda_env_2.png) + +``` +conda remove -n xiaozhi-esp32-server --all -y +conda create -n xiaozhi-esp32-server python=3.10 -y +conda activate xiaozhi-esp32-server + +# 添加清华源通道 +conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main +conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free +conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge + +conda install libopus -y +conda install ffmpeg -y +``` + +请注意,以上命令,不是一股脑执行就成功的,你需要一步步执行,每一步执行完后,都检查一下输出的日志,查看是否成功。 + +## 6.安装本项目依赖 + +你先要下载本项目源码,源码可以通过`git clone`命令下载,如果你不熟悉`git clone`命令。 + +你可以用浏览器打开这个地址`https://github.com/xinnan-tech/xiaozhi-esp32-server.git` + +打开完,找到页面中一个绿色的按钮,写着`Code`的按钮,点开它,然后你就看到`Download ZIP`的按钮。 + +点击它,下载本项目源码压缩包。下载到你电脑后,解压它,此时它的名字可能叫`xiaozhi-esp32-server-main` +你需要把它重命名成`xiaozhi-esp32-server`,在这个文件里,进入到`main`文件夹,再进入到`xiaozhi-server`,好了请记住这个目录`xiaozhi-server`。 + +``` +# 继续使用conda环境 +conda activate xiaozhi-esp32-server +# 进入到你的项目根目录,再进入main/xiaozhi-server +cd main/xiaozhi-server +pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ +pip install -r requirements.txt +``` + +### 7.下载语音识别模型文件 + +本项目语音识别模型,默认使用`SenseVoiceSmall`模型,进行语音转文字。因为模型较大,需要独立下载,下载后把`model.pt` +文件放在`models/SenseVoiceSmall` +目录下。下面两个下载路线任选一个。 + +- 线路一:阿里魔塔下载[SenseVoiceSmall](https://modelscope.cn/models/iic/SenseVoiceSmall/resolve/master/model.pt) +- 线路二:百度网盘下载[SenseVoiceSmall](https://pan.baidu.com/share/init?surl=QlgM58FHhYv1tFnUT_A8Sg&pwd=qvna) 提取码: + `qvna` + +## 8.配置项目文件 + +使用超级管理员账号,登录智控台 ,在顶部菜单找到`参数管理`,找到列表中第一条数据,参数编码是`server.secret`,复制它到`参数值`。 + +`server.secret`需要说明一下,这个`参数值`很重要,作用是让我们的`Server`端连接`manager-api`。`server.secret`是每次从零部署manager模块时,会自动随机生成的密钥。 + +如果你的`xiaozhi-server`目录没有`data`,你需要创建`data`目录。 +如果你的`data`下面没有`.config.yaml`文件,你可以把`xiaozhi-server`目录下的`config_from_api.yaml`文件复制到`data`,并重命名为`.config.yaml` + +复制`参数值`后,打开`xiaozhi-server`下的`data`目录的`.config.yaml`文件。此刻你的配置文件内容应该是这样的: + +``` +manager-api: + url: http://127.0.0.1:8002/xiaozhi + secret: 你的server.secret值 +``` + +把你刚才从`智控台`复制过来的`server.secret`的`参数值`复制到`.config.yaml`文件里的`secret`里。 + +类似这样的效果 +``` +manager-api: + url: http://127.0.0.1:8002/xiaozhi + secret: 12345678-xxxx-xxxx-xxxx-123456789000 +``` + +## 5.运行项目 + +``` +# 确保在xiaozhi-server目录下执行 +conda activate xiaozhi-esp32-server +python app.py +``` + +如果你能看到,类似以下日志,则是本项目服务启动成功的标志。 + +``` +25-02-23 12:01:09[core.websocket_server] - INFO - Server is running at ws://xxx.xx.xx.xx:8000/xiaozhi/v1/ +25-02-23 12:01:09[core.websocket_server] - INFO - =======上面的地址是websocket协议地址,请勿用浏览器访问======= +25-02-23 12:01:09[core.websocket_server] - INFO - 如想测试websocket请用谷歌浏览器打开test目录下的test_page.html +25-02-23 12:01:09[core.websocket_server] - INFO - ======================================================= +``` + +由于你是全模块部署,因此你有两个重要的接口。 + +OTA接口: +``` +http://你电脑局域网的ip:8002/xiaozhi/ota/ +``` + +Websocket接口: +``` +ws://你电脑局域网的ip:8000/xiaozhi/v1/ +``` + +请你务必把以上两个接口地址写入到智控台中:他们将会影响websocket地址发放和自动升级功能。 + +1、使用超级管理员账号,登录智控台,在顶部菜单找到`参数管理`,找到参数编码是`server.websocket`,输入你的`Websocket接口`。 + +2、使用超级管理员账号,登录智控台,在顶部菜单找到`参数管理`,找到数编码是`server.ota`,输入你的`OTA接口`。 + + +接下来,你就可以开始操作你的esp32设备了,你可以`自行编译esp32固件`也可以配置使用`虾哥编译好的1.6.1以上版本的固件`。两个任选一个 + +1、 [编译自己的esp32固件](firmware-build.md)了。 + +2、 [基于虾哥编译好的固件配置自定义服务器](firmware-setting.md)了。 + +# 常见问题 + +以下是一些常见问题,供参考: + +[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md) + +[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md) + +[3、TTS 经常失败,经常超时](./FAQ.md) + +[4、使用Wifi能连接自建服务器,但是4G模式却接不上](./FAQ.md) + +[5、如何提高小智对话响应速度?](./FAQ.md) + +[6、我说话很慢,停顿时小智老是抢话](./FAQ.md) + +[7、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md) diff --git a/docs/FAQ.md b/docs/FAQ.md index 9be27089..f04f3d78 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -102,48 +102,14 @@ VAD: ### 7、我想通过小智控制电灯、空调、远程开关机等操作 💡 -本项目,支持以工具调用的方式控制HomeAssistant设备 +参考教程[ESP32设备与HomeAssistant集成指南](./homeassistant-integration.md) -1、首先选择一款支持function call支持的LLM,例如`ChatGLMLLM`。 +### 8、如何开启手机注册智控台 📱 -2、在配置文件中,将 `selected_module.Intent` 设置为 `function_call`。 +参考教程[阿里云短信集成指南](./ali-sms-integration.md) -3、登录`HomeAssistant`,点击`左下角个人`,切换`安全`导航栏,划到底部`长期访问令牌`生成api_key。 +### 9、更多问题,可联系我们反馈 💬 -在配置文件中,配置好你的home assistant的`devices`(被控制的设备)和`api_key`和`base_url`等信息。例如: +可以在[issues](https://github.com/xinnan-tech/xiaozhi-esp32-server/issues)提交您的问题。 -``` yaml -plugins - home_assistant: - devices: - - 客厅,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1 - - 卧室,台灯,switch.iot_cn_831898993_socn1_on_p_2_1 - base_url: http://你的homeassistant地址:8123 - api_key: 你的home assistant api访问令牌 -``` - -最后,允许function_call 插件在配置文件中启用`hass_get_state`(必须)、`hass_set_state`(必须)、`hass_play_music`(不想用ha听音乐可以不启动),例如: - -``` yaml -Intent: - ... - function_call: - type: nointent - functions: - - change_role - - get_weather - - get_news - - hass_get_state - - hass_set_state - - hass_play_music -``` - -### 8、更多问题,可联系我们反馈 💬 - -我们的联系方式放在[百度网盘中,点击前往](https://pan.baidu.com/s/1x6USjvP1nTRsZ45XlJu65Q),提取码是`223y`。 - -网盘里有"硬件烧录QQ群"、"开源服务端交流群"、"产品建议联系人" 三张图片,请根据需要选择加入。 - -- 硬件烧录QQ群:适用于硬件烧录问题 -- 开源服务端交流群:适用于服务端问题 -- 产品建议联系人:适用于产品功能、产品设计等建议 \ No newline at end of file +也可以发邮件我们取得联系:huangrongzhuang@xin-nan.com \ No newline at end of file diff --git a/docs/ali-sms-integration.md b/docs/ali-sms-integration.md new file mode 100644 index 00000000..1be27787 --- /dev/null +++ b/docs/ali-sms-integration.md @@ -0,0 +1,44 @@ +# 阿里云短信集成指南 + +登录阿里云控制台,进入“短信服务”页面:https://dysms.console.aliyun.com/overview + +## 第一步 添加签名 +![步骤](images/alisms/sms-01.png) +![步骤](images/alisms/sms-02.png) + +以上步骤,会得到签名,请把它写入到智控台参数,`aliyun.sms.sign_name` + +## 第二步 添加模版 +![步骤](images/alisms/sms-11.png) + +以上步骤,会得到模版code,请把它写入到智控台参数,`aliyun.sms.sms_code_template_code` + +注意,签名要等7个工作日,等运营商报备成功后才能发送成功。 + +注意,签名要等7个工作日,等运营商报备成功后才能发送成功。 + +注意,签名要等7个工作日,等运营商报备成功后才能发送成功。 + +可以等报备成功后,再继续往下操作。 + +## 第三步 创建短信账户和开通权限 + +登录阿里云控制台,进入“访问控制”页面:https://ram.console.aliyun.com/overview?activeTab=overview + +![步骤](images/alisms/sms-21.png) +![步骤](images/alisms/sms-22.png) +![步骤](images/alisms/sms-23.png) +![步骤](images/alisms/sms-24.png) +![步骤](images/alisms/sms-25.png) + +以上步骤,会得到access_key_id和access_key_secret,请把它写入到智控台参数,`aliyun.sms.access_key_id`、`aliyun.sms.access_key_secret` +## 第四步 启动手机注册功能 + +1、正常来说,以上信息都填完后,会有这个效果,如果没有,可能缺少了某个步骤 + +![步骤](images/alisms/sms-31.png) + +2、开启允许非管理员用户可注册,将参数`server.allow_user_register`设置成`true` + +3、开启手机注册功能,将参数`server.enable_mobile_register`设置成`true` +![步骤](images/alisms/sms-32.png) \ No newline at end of file diff --git a/docs/docker/nginx.conf b/docs/docker/nginx.conf index 6a746260..9b9c3889 100644 --- a/docs/docker/nginx.conf +++ b/docs/docker/nginx.conf @@ -1,26 +1,53 @@ -server { - listen 8002; - server_name localhost; +user root; +worker_processes 4; - # 静态资源服务(Vue项目) - location / { - root /usr/share/nginx/html; - try_files $uri $uri/ /index.html; - } +events { + worker_connections 1024; +} - # API反向代理(Java项目) - location /xiaozhi/ { - proxy_pass http://127.0.0.1:8003; - proxy_set_header Host $host; - proxy_cookie_path /api/ /; - proxy_set_header Referer $http_referer; - proxy_set_header Cookie $http_cookie; +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 300; + client_header_timeout 180s; + client_body_timeout 180s; + client_max_body_size 1024M; - proxy_connect_timeout 10; - proxy_send_timeout 10; - proxy_read_timeout 10; + gzip on; + gzip_buffers 32 4K; + gzip_comp_level 6; + gzip_min_length 100; + gzip_types application/javascript text/css text/xml image/jpeg image/gif image/png; + gzip_disable "MSIE [1-6]\."; + gzip_vary on; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + server { + # 无域名访问,就用localhost + server_name localhost; + # 80端口 + listen 8002; + + # 转发到编译后到web目录 + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + } + + # 转发到manager-api + location /xiaozhi/ { + proxy_pass http://127.0.0.1:8003; + proxy_set_header Host $host; + proxy_cookie_path /manager/ /; + proxy_set_header Referer $http_referer; + proxy_set_header Cookie $http_cookie; + + proxy_connect_timeout 10; + proxy_send_timeout 10; + proxy_read_timeout 10; + + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } } } \ No newline at end of file diff --git a/docs/firmware-build.md b/docs/firmware-build.md index 9187681e..fffac288 100644 --- a/docs/firmware-build.md +++ b/docs/firmware-build.md @@ -1,39 +1,81 @@ -# 编译esp32固件 +# esp32固件编译 -1. 先按照这个教程配置项目环境[《Windows搭建 ESP IDF 5.3.2开发环境以及编译小智》](https://icnynnzcwou8.feishu.cn/wiki/JEYDwTTALi5s2zkGlFGcDiRknXf) +## 第1步 准备你的ota地址 -2. 配置好编译环境后,下载虾哥[xiaozhi-esp32](https://github.com/78/xiaozhi-esp32)项目源码,进入虾哥项目,打开`xiaozhi-esp32/main/Kconfig.projbuild`文件,找到`WEBSOCKET_URL`的`default`的内容,把`wss://api.tenclass.net` - 改成你自己的地址,例如,我的接口地址是`ws://192.168.1.25:8000/xiaozhi/v1/`,就把内容改成这个。 +如果你,使用的是本项目0.3.12版本,不管是简单Server部署还是全模块部署,都会有ota地址。 + +由于简单Server部署和全模块部署的OTA地址设置方式不一样,请你选择下面的具体方式: + +### 如果你用的是简单Server部署 +此刻,请你用浏览器打开你的ota地址,例如我的ota地址 +``` +http://192.168.1.25:8002/xiaozhi/ota/ +``` +如果显示“OTA接口运行正常,向设备发送的websocket地址是:ws://xxx:8000/xiaozhi/v1/ + +你可以使用项目自带的`test_page.html`测试一下,是否能连上ota页面输出的websocket地址。 + +如果访问不到,你需要到配置文件`.config.yaml`里修改`server.websocket`的地址,重启后再重新测试,直到`test_page.html`能正常访问。 + +成功后,请往下进行第2步 + +### 如果你用的是全模块部署 +此刻,请你用浏览器打开你的ota地址,例如我的ota地址 +``` +http://192.168.1.25:8002/xiaozhi/ota/ +``` + +如果显示“OTA接口运行正常,websocket集群数量:X”。那就往下进行2步。 + +如果显示“OTA接口运行不正常”,大概是你还没在`智控台`配置`Websocket`地址。那就: + +- 1、使用超级管理员登录智控台 + +- 2、顶部菜单点击`参数管理` + +- 3、在列表中找到`server.websocket`项目,输入你的`Websocket`地址。例如我的就是 + +``` +ws://192.168.1.25:8000/xiaozhi/v1/ +``` + +配置完后,再使用浏览器刷新你的ota接口地址,看看是不是正常了。如果还不正常就,就再次确认一下Websocket是否正常启动,是否配置了Websocket地址。 + +## 第2步 配置环境 +先按照这个教程配置项目环境[《Windows搭建 ESP IDF 5.3.2开发环境以及编译小智》](https://icnynnzcwou8.feishu.cn/wiki/JEYDwTTALi5s2zkGlFGcDiRknXf) + +## 第3步 打开配置文件 +配置好编译环境后,下载虾哥iaozhi-esp32项目源码, + +从这里下载虾哥[xiaozhi-esp32项目源码](https://github.com/78/xiaozhi-esp32)。 + +下载后,打开`xiaozhi-esp32/main/Kconfig.projbuild`文件。 + +## 第4步 修改OTA地址 + +找到`OTA_URL`的`default`的内容,把`https://api.tenclass.net/xiaozhi/ota/` + 改成你自己的地址,例如,我的接口地址是`http://192.168.1.25:8002/xiaozhi/ota/`,就把内容改成这个。 修改前: - ``` -config WEBSOCKET_URL - depends on CONNECTION_TYPE_WEBSOCKET - string "Websocket URL" - default "wss://api.tenclass.net/xiaozhi/v1/" +config OTA_URL + string "Default OTA URL" + default "https://api.tenclass.net/xiaozhi/ota/" help - Communication with the server through websocket after wake up. + The application will access this URL to check for new firmwares and server address. ``` - -修改后(示例): - +修改后: ``` -config WEBSOCKET_URL - depends on CONNECTION_TYPE_WEBSOCKET - string "Websocket URL" - default "ws://192.168.1.25:8000/xiaozhi/v1/" +config OTA_URL + string "Default OTA URL" + default "http://192.168.1.25:8002/xiaozhi/ota/" help - Communication with the server through websocket after wake up. + The application will access this URL to check for new firmwares and server address. ``` -注意:你的地址是`ws://`开头,不是`wss://`开头,一定不要写错了。 +## 第4步 设置编译参数 -注意:你的地址是`ws://`开头,不是`wss://`开头,一定不要写错了。 - -注意:你的地址是`ws://`开头,不是`wss://`开头,一定不要写错了。 - -3. 设置编译参数 +设置编译参数 ``` # 终端命令行进入xiaozhi-esp32的根目录 @@ -44,40 +86,29 @@ idf.py set-target esp32s3 idf.py menuconfig ``` -![图片](images/build_setting01.png) - -进入菜单配置后,再进入`Xiaozhi Assistant`,将`CONNECTION_TYPE`设置为`Websocket` -回退到主菜单,再进入`Xiaozhi Assistant`,将`BOARD_TYPE`设置你板子的具体型号 +进入菜单配置后,再进入`Xiaozhi Assistant`,将`BOARD_TYPE`设置你板子的具体型号 保存退出,回到终端命令行。 -![图片](images/build_setting02.png) - -4. 编译固件 +## 第5步 编译固件 ``` idf.py build ``` -如果是vscode安装的idf可以使用`F1`或者`ctrl+shift+p`,输入idf然后直接选择进行编译 - -还可以直接进行烧录不用接下来的操作 - - - -5. 打包bin固件 +## 第6步 打包bin固件 ``` cd scripts python release.py ``` -编译成功后,会在项目根目录下的`build`目录下生成固件文件`merged-binary.bin`。 +上面的打包命令执行完成后,会在项目根目录下的`build`目录下生成固件文件`merged-binary.bin`。 这个`merged-binary.bin`就是要烧录到硬件上的固件文件。 注意:如果执行到第二命令后,报了“zip”相关的错误,请忽略这个错误,只要`build`目录下生成固件文件`merged-binary.bin` ,对你没有太大影响,请继续。 -6. 烧录固件 +## 第7步 烧录固件 将esp32设备连接电脑,使用chrome浏览器,打开以下网址 ``` @@ -89,6 +120,7 @@ https://espressif.github.io/esp-launchpad/ 烧录成功且联网成功后,通过唤醒词唤醒小智,留意server端输出的控制台信息。 +## 常见问题 以下是一些常见问题,供参考: [1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md) diff --git a/docs/firmware-setting.md b/docs/firmware-setting.md new file mode 100644 index 00000000..0ed350a0 --- /dev/null +++ b/docs/firmware-setting.md @@ -0,0 +1,54 @@ +# 基于虾哥编译好的固件配置自定义服务器 + +## 第1步 确认版本 +烧录虾哥已经编译好的[1.6.1版本以上固件](https://github.com/78/xiaozhi-esp32/releases) + +## 第2步 准备你的ota地址 +如果你按照教程使用的是全模块部署,就应该会有ota地址。 + +此刻,请你用浏览器打开你的ota地址,例如我的ota地址 +``` +https://2662r3426b.vicp.fun/xiaozhi/ota/ +``` + +如果显示“OTA接口运行正常,websocket集群数量:X”。那就往下。 + +如果显示“OTA接口运行不正常”,大概是你还没在`智控台`配置`Websocket`地址。那就: + +- 1、使用超级管理员登录智控台 + +- 2、顶部菜单点击`参数管理` + +- 3、在列表中找到`server.websocket`项目,输入你的`Websocket`地址。例如我的就是 + +``` +wss://2662r3426b.vicp.fun/xiaozhi/v1/ +``` + +配置完后,再使用浏览器刷新你的ota接口地址,看看是不是正常了。如果还不正常就,就再次确认一下Websocket是否正常启动,是否配置了Websocket地址。 + +## 第3步 进入配网模式 +进入机器的配网模式,在页面顶部,点击“高级选项”,在里面输入你服务器的`ota`地址,点击保存。重启设备 +![请参考-OTA地址设置](../docs/images/firmware-setting-ota.png) + +## 第4步 唤醒小智,查看日志输出 + +唤醒小智,看看日志是不是正常输出。 + + +## 常见问题 +以下是一些常见问题,供参考: + +[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md) + +[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md) + +[3、TTS 经常失败,经常超时](./FAQ.md) + +[4、使用Wifi能连接自建服务器,但是4G模式却接不上](./FAQ.md) + +[5、如何提高小智对话响应速度?](./FAQ.md) + +[6、我说话很慢,停顿时小智老是抢话](./FAQ.md) + +[7、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md) diff --git a/docs/fish-speech-integration.md b/docs/fish-speech-integration.md new file mode 100644 index 00000000..b916e619 --- /dev/null +++ b/docs/fish-speech-integration.md @@ -0,0 +1,72 @@ +登录AutoDL,租赁镜像 +选择镜像: +``` +PyTorch / 2.1.0 / 3.10(ubuntu22.04) / cuda 12.1 +``` + +机器开机后,设置学术加速 +``` +source /etc/network_turbo +``` + +进入工作目录 +``` +cd autodl-tmp/ +``` + +拉取项目 +``` +git clone https://gitclone.com/github.com/fishaudio/fish-speech.git ; cd fish-speech +``` + +安装依赖 +``` +pip install -e. +``` + +如果报错,安装portaudio +``` +apt-get install portaudio19-dev -y +``` + +安装后执行 +``` +pip install torch==2.3.1 torchvision==0.18.1 torchaudio==2.3.1 --index-url https://download.pytorch.org/whl/cu121 +``` + +下载模型 +``` +cd tools +python download_models.py +``` + +下载完模型后运行接口 +``` +python -m tools.api_server --listen 0.0.0.0:6006 +``` + +然后用浏览器去到aotodl实例页面 +``` +https://autodl.com/console/instance/list +``` + +如下图点击你刚才机器的`自定义服务`按钮,开启端口转发服务 +![自定义服务](images/fishspeech/autodl-01.png) + +端口转发服务设置完成后,你本地电脑打开网址`http://localhost:6006/`,就可以访问fish-speech的接口了 +![服务预览](images/fishspeech/autodl-02.png) + + +如果你是单模块部署,核心配置如下 +``` +selected_module: + TTS: FishSpeech +TTS: + FishSpeech: + reference_audio: ["config/assets/wakeup_words.wav",] + reference_text: ["哈啰啊,我是小智啦,声音好听的台湾女孩一枚,超开心认识你耶,最近在忙啥,别忘了给我来点有趣的料哦,我超爱听八卦的啦",] + api_key: "123" + api_url: "http://127.0.0.1:6006/v1/tts" +``` + +然后重启服务 \ No newline at end of file diff --git a/docs/homeassistant-integration.md b/docs/homeassistant-integration.md new file mode 100644 index 00000000..3142244e --- /dev/null +++ b/docs/homeassistant-integration.md @@ -0,0 +1,248 @@ +# 小智ESP32-开源服务端与HomeAssistant集成指南 + +[TOC] + +----- + +## 简介 + +本文档将指导您如何将ESP32设备与HomeAssistant进行集成。 + +## 前提条件 + +- 已安装并配置好`HomeAssistant` +- 本次我选择的模型是:免费的ChatGLM,它支持functioncall函数调用 + +## 开始前的操作(必要) + +### 1. 获取HA的网络网络地址信息 + +请访问你Home Assistant的网络地址,例如,我的HA的地址是192.168.4.7,端口则是默认的8123,则在浏览器打开 + +``` +http://192.168.4.7:8123 +``` + +> 手动查询 HA 的 IP 地址方法**(仅限小智esp32-server和HA部署在同一个网络设备[例如同一个wifi]下)**: +> +> 1. 进入 Home Assistant(前端)。 +> +> 2. 点击左下角 **设置(Settings)** → **系统(System)** → **网络(Network)**。 +> +> 3. 滑到最底部`Home Assistant 网址(Home Assistant website)`区域,在`本地网络(local network)`中,点击`眼睛`按钮,可以看到当前使用的 IP 地址(如 `192.168.1.10`)和网络接口。点击`复制连接(copy link)`可以直接复制。 +> +> ![image-20250504051716417](images/image-ha-integration-01.png) + +或,您已经设置了直接可以访问的Home Assistant的OAuth地址,您也可以在浏览器内直接访问 + +``` +http://homeassistant.local:8123 +``` + +### 2. 登录`Home Assistant`拿到开发密钥 + +登录`HomeAssistant`,点击`左下角头像 -> 个人`,切换`安全`导航栏,划到底部`长期访问令牌`生成api_key,并复制保存,后续的方法都需要使用这个api key且仅出现一次(小tips: 您可以保存生成的二维码图像,后续可以扫描二维码再此提取api key)。 + +## 方法1:小智社区共建的HA调用功能 + +### 功能描述 + +- 如您后续需要增加新的设备,该方法需要手动重启`xiaozhi-esp32-server服务端`以此更新设备信息**(重要**)。 + +- 需要您确保已经在HomeAssistant中集成`Xiaomi Home`,并将米家的设备导入进`HomeAssistant`。 + +- 需要您确保`xiaozhi-esp32-server智控台`能正常使用。 + +- 我的`xiaozhi-esp32-server智控台`和`HomeAssistant`部署在同一台机器的另一个端口,版本是`0.3.10` + + ``` + http://192.168.4.7:8002 + ``` + + +### 配置步骤 + +#### 1. 登录`HomeAssistant`整理需要控制的设备清单 + +登录`HomeAssistant`,点击`左下角的设置`,然后进入`设备与服务`,再点击顶部的`实体`。 + +然后在实体中搜索你相关控制的开关,结果出来后,在列表中,点击其中一个结果,这是会出现一个开关的界面。 + +在开关的界面,我们尝试点击开关,看看是开发会随着我们的点击开/关。如果能操作,说明是正常联网的。 + +接着在开关面板找到设置按钮,点击后,可以查看这个开关的`实体标识符`。 + +我们打开一个记事本,按照这样格式整理一条数据: + +位置+英文逗号+设备名称+英文逗号+`实体标识符`+英文分号 + +例如,我在公司,我有一个玩具灯,他的标识符是switch.cuco_cn_460494544_cp1_on_p_2_1,那么就这个写这一条数据 + +``` +公司,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1; +``` + +当然最后我可能要操作两个灯,我的最终的结果是: + +``` +公司,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1; +公司,台灯,switch.iot_cn_831898993_socn1_on_p_2_1; +``` + +这段字符,我们成为“设备清单字符”需要保存好,等一下有用。 + + +#### 2. 登录`智控台` + +使用管理员账号,登录`智控台`。点击顶部菜单`参数管理`,搜索`plugins.home_assistant.`,会有三条结果出来 + +编辑`plugins.home_assistant.devices`,把刚才整理的设备清单字符粘贴进去。 + + +编辑`plugins.home_assistant.base_url`,把你部署的`HomeAssistant`接口地址粘贴进去,我粘贴进去的地址是这样的 + +``` +http://192.168.4.7:8123 +``` + +编辑`plugins.home_assistant.api_key`,把你从`HomeAssistant`复制过来的密钥,粘贴进去 + + +#### 3. 设置`意图识别`函数 + +在智控台,点击顶部菜单“模型配置”,在左侧栏,找到“意图识别”,找到id为`Intent_function_call`的意图,点击编辑 + +然后在弹框中,在原来的基础上追加两个函数:“hass_get_state”和“hass_set_state” + +修改前 +``` +change_role;get_weather;get_news;play_music +``` + +修改后 + +``` +change_role;get_weather;get_news;play_music;hass_get_state;hass_set_state +``` + +#### 4. 手动重启xiaozhi-server + +重启xiaozhi-server程序 + + +#### 5. 确认角色配置是否设置了函数意图识别 + +在智控台,点击顶部菜单“智能体管理”,找到设备所在的智能体,点击“配置角色” + +确认意图识别(Intent),是否选择“函数调用意图识别” + + +#### 6. 唤醒设别进行控制 + +尝试和esp32说,“打开XXX灯” + +## 方法2:小智将Home Assistant的语音助手作为LLM工具 + +### 功能描述 + +- 该方法有一个比较严重的缺点——**该方法无法使用小智开源生态的function_call插件功能的能力**,因为使用Home Assistant作为小智的LLM工具会将意图识别能力转让给Home Assistant。但是**这个方法是能体验到原生的Home Assistant操作功能,且小智的聊天能力不变**。如实在介意可以使用同样是Home Assistant支持的[方法3](##方法3:使用Home Assistant的MCP服务(推荐)),能够最大程度体验到Home Assistant的功能。 + +### 配置步骤: + +#### 1. 配置Home Assistant的大模型语音助手。 + +**需要您提前配置好Home Assistant的语音助手或大模型工具。** + +#### 2. 获取Home Assistant的语言助手的Agent ID. + +1. 进入Home Assistant页面内。左侧点击`开发者助手`。 +2. 在打开的`开发者助手`内,点击`动作`选项卡(如图示操作1),在页面内的选项栏`动作`中,找到或输入`conversation.process(对话-处理)`并选择`对话(conversation): 处理`(如图示操作2)。 + +![image-20250504043539343](images/image-ha-integration-02.png) + +3. 在页面内勾选`代理(agent)`选项,在变成常亮的`对话代理(conversation agent)`内选择您步骤一配置好的语音助手名称,如图示,我这边配置好的是`ZhipuAi`并选择。 + +![image-20250504043854760](images/image-ha-integration-03.png) + +4. 选中后,点击表单左下方的`进入YAML模式`。 + +![image-20250504043951126](images/image-ha-integration-04.png) + +5. 复制其中的agent-id的值,例如图示中我的是`01JP2DYMBDF7F4ZA2DMCF2AGX2`(仅供参考)。 + +![image-20250504044046466](images/image-ha-integration-05.png) + +6. 切换到小智开源服务端`xiaozhi-esp32-server`的`config.yaml`文件内,在LLM配置中,找到Home Assistant,设置您的Home Assistant的网络地址,Api key和刚刚查询到的agent_id。 +7. 修改`config.yaml`文件内的`selected_module`属性的`LLM`为`HomeAssistant`,`Intent`为`nointent`。 +8. 重启小智开源服务端`xiaozhi-esp32-server`即可正常使用。 + +## 方法3:使用Home Assistant的MCP服务(推荐) + +### 功能描述 + +- 需要您提前在Home Assistant内集成并安装好HA集成——[Model Context Protocol Server](https://www.home-assistant.io/integrations/mcp_server/)。 + +- 这个方法与方法2都是HA官方提供的解决方法,与方法2不同的是,您可以正常使用小智开源服务端`xiaozhi-esp32-server`的开源共建的插件,同时允许您随意使用任何一个支持function_call功能的LLM大模型。 + +### 配置步骤 + +#### 1. 安装Home Assistant的MCP服务集成。 + +集成官方网址——[Model Context Protocol Server](https://www.home-assistant.io/integrations/mcp_server/)。。 + +或跟随以下手动操作。 + +> - 前往Home Assistant页面的**[设置 > 设备和服务(Settings > Devices & Services.)](https://my.home-assistant.io/redirect/integrations)**。 +> +> - 在右下角,选择 **[添加集成(Add Integration)](https://my.home-assistant.io/redirect/config_flow_start?domain=mcp_server)**按钮。 +> +> - 从列表中选择**模型上下文协议服务器(Model Context Protocol Server)**。 +> +> - 按照屏幕上的说明完成设置。 + +#### 2. 配置小智开源服务端MCP配置信息 + +切换到小智开源服务端`xiaozhi-esp32-server`的`mcp_server_settings.json`文件内,在`"mcpServers"`的括号内添加以下内容: + +```json +"Home Assistant": { + "command": "mcp-proxy", + "args": [ + "http://YOUR_HA_HOST/mcp_server/sse" + ], + "env": { + "API_ACCESS_TOKEN": "YOUR_API_ACCESS_TOKEN" + } +}, +``` + +注意: + +1. **替换配置:** + - 替换`args`内的`YOUR_HA_HOST`为您的HA服务地址,如果你的服务地址已经包含了https/http字样(例如`http://192.168.1.101:8123`),则只需要填入`192.168.1.101:8123`即可。 + - 将`env`内`API_ACCESS_TOKEN`的`YOUR_API_ACCESS_TOKEN`替换成您之前获取到的开发密钥api key。 +2. **如果你添加配置是在`"mcpServers"`的括号内后续没有新的`mcpServers`的配置时,需要把最后的逗号`,`移除**,否则可能会解析失败。 + +**最后效果参考以下(参考如下)**: + +```json + "mcpServers": { + "Home Assistant": { + "command": "mcp-proxy", + "args": [ + "http://192.168.1.101:8123/mcp_server/sse" + ], + "env": { + "API_ACCESS_TOKEN": "abcd.efghi.jkl" + } + } + } +``` + +#### 3. 配置小智开源服务端的系统配置 + +1. **选择任意一款支持function_call的LLM大模型作为小智的LLM聊天助手(但不要选择Home Assistant作为LLM工具)**,本次我选择的模型是:免费的ChatGLM,它支持functioncall函数调用,但部分时候调用不太稳定,如果像追求稳定建议把LLM设置成:DoubaoLLM,使用的具体model_name是:doubao-1-5-pro-32k-250115。 + +2. 切换到小智开源服务端`xiaozhi-esp32-server`的`config.yaml`文件内,设置您的LLM大模型配置,并且将`selected_module`配置的`Intent`调整为`function_call`。 + +3. 重启小智开源服务端`xiaozhi-esp32-server`即可正常使用。 \ No newline at end of file diff --git a/docs/images/alisms/sms-01.png b/docs/images/alisms/sms-01.png new file mode 100644 index 00000000..89c56f50 Binary files /dev/null and b/docs/images/alisms/sms-01.png differ diff --git a/docs/images/alisms/sms-02.png b/docs/images/alisms/sms-02.png new file mode 100644 index 00000000..22537f36 Binary files /dev/null and b/docs/images/alisms/sms-02.png differ diff --git a/docs/images/alisms/sms-11.png b/docs/images/alisms/sms-11.png new file mode 100644 index 00000000..05fcaf57 Binary files /dev/null and b/docs/images/alisms/sms-11.png differ diff --git a/docs/images/alisms/sms-21.png b/docs/images/alisms/sms-21.png new file mode 100644 index 00000000..5bfc2801 Binary files /dev/null and b/docs/images/alisms/sms-21.png differ diff --git a/docs/images/alisms/sms-22.png b/docs/images/alisms/sms-22.png new file mode 100644 index 00000000..3d83984b Binary files /dev/null and b/docs/images/alisms/sms-22.png differ diff --git a/docs/images/alisms/sms-23.png b/docs/images/alisms/sms-23.png new file mode 100644 index 00000000..584d8d67 Binary files /dev/null and b/docs/images/alisms/sms-23.png differ diff --git a/docs/images/alisms/sms-24.png b/docs/images/alisms/sms-24.png new file mode 100644 index 00000000..1b22c359 Binary files /dev/null and b/docs/images/alisms/sms-24.png differ diff --git a/docs/images/alisms/sms-25.png b/docs/images/alisms/sms-25.png new file mode 100644 index 00000000..3bb68cfa Binary files /dev/null and b/docs/images/alisms/sms-25.png differ diff --git a/docs/images/alisms/sms-31.png b/docs/images/alisms/sms-31.png new file mode 100644 index 00000000..7f0ee6c4 Binary files /dev/null and b/docs/images/alisms/sms-31.png differ diff --git a/docs/images/alisms/sms-32.png b/docs/images/alisms/sms-32.png new file mode 100644 index 00000000..db06cc4d Binary files /dev/null and b/docs/images/alisms/sms-32.png differ diff --git a/docs/images/build_setting01.png b/docs/images/build_setting01.png deleted file mode 100644 index 24e2aeec..00000000 Binary files a/docs/images/build_setting01.png and /dev/null differ diff --git a/docs/images/build_setting02.png b/docs/images/build_setting02.png deleted file mode 100644 index 791a5b05..00000000 Binary files a/docs/images/build_setting02.png and /dev/null differ diff --git a/docs/images/deploy.png b/docs/images/deploy.png deleted file mode 100644 index e1db8a48..00000000 Binary files a/docs/images/deploy.png and /dev/null differ diff --git a/docs/images/deploy1.png b/docs/images/deploy1.png new file mode 100644 index 00000000..70488bcf Binary files /dev/null and b/docs/images/deploy1.png differ diff --git a/docs/images/deploy2.png b/docs/images/deploy2.png new file mode 100644 index 00000000..61c36997 Binary files /dev/null and b/docs/images/deploy2.png differ diff --git a/docs/images/firmware-setting-ota.png b/docs/images/firmware-setting-ota.png new file mode 100644 index 00000000..c151fd93 Binary files /dev/null and b/docs/images/firmware-setting-ota.png differ diff --git a/docs/images/fishspeech/autodl-01.png b/docs/images/fishspeech/autodl-01.png new file mode 100644 index 00000000..695b94b8 Binary files /dev/null and b/docs/images/fishspeech/autodl-01.png differ diff --git a/docs/images/fishspeech/autodl-02.png b/docs/images/fishspeech/autodl-02.png new file mode 100644 index 00000000..e3f72afe Binary files /dev/null and b/docs/images/fishspeech/autodl-02.png differ diff --git a/docs/images/image-ha-integration-01.png b/docs/images/image-ha-integration-01.png new file mode 100644 index 00000000..eb05461a Binary files /dev/null and b/docs/images/image-ha-integration-01.png differ diff --git a/docs/images/image-ha-integration-02.png b/docs/images/image-ha-integration-02.png new file mode 100644 index 00000000..7431defa Binary files /dev/null and b/docs/images/image-ha-integration-02.png differ diff --git a/docs/images/image-ha-integration-03.png b/docs/images/image-ha-integration-03.png new file mode 100644 index 00000000..3f1aa596 Binary files /dev/null and b/docs/images/image-ha-integration-03.png differ diff --git a/docs/images/image-ha-integration-04.png b/docs/images/image-ha-integration-04.png new file mode 100644 index 00000000..29b402e5 Binary files /dev/null and b/docs/images/image-ha-integration-04.png differ diff --git a/docs/images/image-ha-integration-05.png b/docs/images/image-ha-integration-05.png new file mode 100644 index 00000000..1aa8ffc2 Binary files /dev/null and b/docs/images/image-ha-integration-05.png differ diff --git a/docs/images/logo_bailing.png b/docs/images/logo_bailing.png new file mode 100644 index 00000000..22ab6e11 Binary files /dev/null and b/docs/images/logo_bailing.png differ diff --git a/docs/images/logo_huiyuan.png b/docs/images/logo_huiyuan.png new file mode 100644 index 00000000..6e441913 Binary files /dev/null and b/docs/images/logo_huiyuan.png differ diff --git a/docs/images/logo_qinren.png b/docs/images/logo_qinren.png new file mode 100644 index 00000000..a01af841 Binary files /dev/null and b/docs/images/logo_qinren.png differ diff --git a/docs/images/logo_tenclass.png b/docs/images/logo_tenclass.png new file mode 100644 index 00000000..86a6dc4a Binary files /dev/null and b/docs/images/logo_tenclass.png differ diff --git a/docs/images/logo_xuanfeng.png b/docs/images/logo_xuanfeng.png new file mode 100644 index 00000000..552ee78a Binary files /dev/null and b/docs/images/logo_xuanfeng.png differ diff --git a/docs/images/vscode_idf.png b/docs/images/vscode_idf.png deleted file mode 100644 index f943b71e..00000000 Binary files a/docs/images/vscode_idf.png and /dev/null differ diff --git a/main/README.md b/main/README.md index ba3a499b..4255ab14 100644 --- a/main/README.md +++ b/main/README.md @@ -1,8 +1,6 @@ -本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](./FAQ.md#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F-) +本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3) # 项目目录介绍 -当你看到这份文件的时候,这个这个项目还没完善好。我们还有很多东西要做。 - 如果你是一名软件开发者,这里有一份[《致开发者的公开信》](../docs/contributor_open_letter.md),欢迎归队! ``` @@ -12,10 +10,10 @@ xiaozhi-esp32-server ├─ manager-api 8002 端口 Java语言开发 负责提供控制台的api ``` -# xiaozhi-server 接口协议 +# xiaozhi-server 和ESP32通讯协议 -[虾哥团队通信协议:Websocket 连接](https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh) +https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh # manager-web 、manager-api接口协议 -https://2662r3426b.vicp.fun/xiaozhi/v1/doc.html +https://2662r3426b.vicp.fun/xiaozhi/doc.html diff --git a/main/manager-api/README.md b/main/manager-api/README.md index bef06b68..484c28dc 100644 --- a/main/manager-api/README.md +++ b/main/manager-api/README.md @@ -1,4 +1,5 @@ -本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../../../docs/Deployment.md) +本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../../README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3) + # 项目介绍 manager-api 该项目基于SpringBoot框架开发。 @@ -12,80 +13,6 @@ MySQL 8.0+ Redis 5.0+ Vue 3.x -# 创建数据库 - -如果本机已经安装了MySQL,可以直接在数据库中创建名为`xiaozhi_esp32_server`的数据库。 - -```sql -CREATE DATABASE xiaozhi_esp32_server CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -``` - -如果还没有MySQL,你可以通过docker安装mysql - -``` -docker run --name xiaozhi-esp32-server-db -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -e MYSQL_DATABASE=xiaozhi_esp32_server -e MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" -d mysql:latest -``` - -# 确认项目数据库连接信息 - -在`src/main/resources/application-dev.yml`中配置数据库连接信息 - -``` -spring: - datasource: - username: root - password: 123456 -``` - - -# 连接Redis - -如果还没有Redis,你可以通过docker安装redis - -``` -docker run --name xiaozhi-esp32-server-redis -d -p 6379:6379 redis -``` - -# 确认项目Redis连接信息 - -在`src/main/resources/application-dev.yml`中配置Redis连接信息 - -``` -spring: - data: - redis: - host: localhost - port: 6379 - password: - database: 0 -``` - - -# 测试启动 - -本项目为SpringBoot项目,启动方式为: -打开`Application.java`运行`Main`方法启动 - -``` -路径地址: -src/main/java/xiaozhi/AdminApplication.java -``` - -# 打包编译 - -执行以下命令生产jar包 - -``` -mvn clean install -``` - -把jar包放在服务器上,执行 - -``` -nohup java -jar xiaozhi-esp32-api.jar --spring.profiles.activate=dev -``` - - # 接口文档 启动后打开:http://localhost:8002/xiaozhi/doc.html diff --git a/main/manager-api/pom.xml b/main/manager-api/pom.xml index 9ad38043..883295bf 100644 --- a/main/manager-api/pom.xml +++ b/main/manager-api/pom.xml @@ -28,6 +28,8 @@ 1.6.2 33.0.0-jre 4.20.0 + 4.1.0 + 3.4.0 @@ -37,6 +39,7 @@ jakarta ${shiro.version} + org.apache.shiro shiro-spring @@ -197,6 +200,22 @@ lombok true + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + com.aliyun + dysmsapi20170525 + ${aliyun-sms-version} + + + + com.squareup.okio + okio + ${okio-version} + diff --git a/main/manager-api/src/main/java/xiaozhi/common/config/AsyncConfig.java b/main/manager-api/src/main/java/xiaozhi/common/config/AsyncConfig.java new file mode 100644 index 00000000..d0cc2dfc --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/common/config/AsyncConfig.java @@ -0,0 +1,40 @@ +package xiaozhi.common.config; + +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@Configuration +@EnableAsync +@EnableAspectJAutoProxy(exposeProxy = true) +public class AsyncConfig { + + @Bean(name = "taskExecutor") + public Executor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(2); + executor.setMaxPoolSize(4); + executor.setQueueCapacity(1000); + executor.setThreadNamePrefix("AsyncThread-"); + // 设置拒绝策略:由调用线程执行 + executor.setRejectedExecutionHandler(new RejectedExecutionHandler() { + @Override + public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { + try { + // 如果线程池已满,则由调用线程执行 + r.run(); + } catch (Exception e) { + throw new RuntimeException("执行异步任务失败", e); + } + } + }); + executor.initialize(); + return executor; + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/common/config/RestTemplateConfig.java b/main/manager-api/src/main/java/xiaozhi/common/config/RestTemplateConfig.java new file mode 100644 index 00000000..ecfaa359 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/common/config/RestTemplateConfig.java @@ -0,0 +1,17 @@ +package xiaozhi.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +/** + * RestTemplate配置 + */ +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/common/config/SwaggerConfig.java b/main/manager-api/src/main/java/xiaozhi/common/config/SwaggerConfig.java index 0def6589..8fb31641 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/config/SwaggerConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/common/config/SwaggerConfig.java @@ -71,6 +71,14 @@ public class SwaggerConfig { .build(); } + @Bean + public GroupedOpenApi configApi() { + return GroupedOpenApi.builder() + .group("config") + .pathsToMatch("/config/**") + .build(); + } + @Bean public OpenAPI customOpenAPI() { return new OpenAPI().info(new Info() diff --git a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java index 3738498b..b3c036ee 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java +++ b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java @@ -1,5 +1,7 @@ package xiaozhi.common.constant; +import lombok.Getter; + /** * 常量 * Copyright (c) 人人开源 All rights reserved. @@ -74,38 +76,59 @@ public interface Constant { */ String ORDER = "order"; + /** + * 请求头授权标识 + */ String AUTHORIZATION = "Authorization"; + /** + * 服务器密钥 + */ + String SERVER_SECRET = "server.secret"; + + /** + * websocket地址 + */ + String SERVER_WEBSOCKET = "server.websocket"; + + /** + * ota地址 + */ + String SERVER_OTA = "server.ota"; + + /** + * 是否允许用户注册 + */ + String SERVER_ALLOW_USER_REGISTER = "server.allow_user_register"; + + /** + * 下发六位验证码时显示的控制面板地址 + */ + String SERVER_FRONTED_URL = "server.fronted_url"; + /** * 路径分割符 */ String FILE_EXTENSION_SEG = "."; + /** + * 无记忆 + */ + String MEMORY_NO_MEM = "Memory_nomem"; + enum SysBaseParam { /** - * 系统全称 + * ICP备案号 */ - SYS_NAME("SYS_NAME"), + BEIAN_ICP_NUM("server.beian_icp_num"), /** - * 系统简称 + * GA备案号 */ - SYS_SHORT_NAME("SYS_SHORT_NAME"), + BEIAN_GA_NUM("server.beian_ga_num"), /** - * 系统描述 + * 系统名称 */ - SYS_DES("SYS_DES"), - /** - * 登录失败几次锁定 - */ - LOGIN_LOCK_COUNT("LOGIN_LOCK_COUNT"), - /** - * 账号失败锁定分钟数 - */ - LOGIN_LOCK_TIME("LOGIN_LOCK_TIME"), - /** - * TOKEN强验证 - */ - SYS_TOKEN_SECURITY("SYS_TOKEN_SECURITY"); + SERVER_NAME("server.name"); private String value; @@ -118,6 +141,46 @@ public interface Constant { } } + /** + * 系统短信 + */ + enum SysMSMParam { + /** + * 阿里云授权keyID + */ + ALIYUN_SMS_ACCESS_KEY_ID("aliyun.sms.access_key_id"), + /** + * 阿里云授权密钥 + */ + ALIYUN_SMS_ACCESS_KEY_SECRET("aliyun.sms.access_key_secret"), + /** + * 阿里云短信签名 + */ + ALIYUN_SMS_SIGN_NAME("aliyun.sms.sign_name"), + /** + * 阿里云短信模板 + */ + ALIYUN_SMS_SMS_CODE_TEMPLATE_CODE("aliyun.sms.sms_code_template_code"), + /** + * 单号码最大短信发送条数 + */ + SERVER_SMS_MAX_SEND_COUNT("server.sms_max_send_count"), + /** + * 是否开启手机注册 + */ + SERVER_ENABLE_MOBILE_REGISTER("server.enable_mobile_register"); + + private String value; + + SysMSMParam(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + /** * 数据状态 */ @@ -145,4 +208,49 @@ public interface Constant { return value; } } + + @Getter + enum ChatHistoryConfEnum { + IGNORE(0, "不记录"), + RECORD_TEXT(1, "记录文本"), + RECORD_TEXT_AUDIO(2, "文本音频都记录"); + + private final int code; + private final String name; + + ChatHistoryConfEnum(int code, String name) { + this.code = code; + this.name = name; + } + } + + /** + * 版本号 + */ + public static final String VERSION = "0.4.4"; + + /** + * 无效固件URL + */ + String INVALID_FIRMWARE_URL = "http://xiaozhi.server.com:8002/xiaozhi/otaMag/download/NOT_ACTIVATED_FIRMWARE_THIS_IS_A_INVALID_URL"; + + /** + * 字典类型 + */ + enum DictType { + /** + * 手机区号 + */ + MOBILE_AREA("MOBILE_AREA"); + + private String value; + + DictType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java b/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java index e5365b3c..6a9af011 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java +++ b/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java @@ -20,7 +20,7 @@ public interface ErrorCode { int ACCOUNT_DISABLE = 10005; int IDENTIFIER_NOT_NULL = 10006; int CAPTCHA_ERROR = 10007; - int SUB_MENU_EXIST = 10008; + int PHONE_NOT_NULL = 10008; int PASSWORD_ERROR = 10009; int SUPERIOR_DEPT_ERROR = 10011; @@ -43,4 +43,16 @@ public interface ErrorCode { int PASSWORD_WEAK_ERROR = 10031; int DEL_MYSELF_ERROR = 10032; int DEVICE_CAPTCHA_ERROR = 10033; + + // 参数校验相关错误码 + int PARAM_VALUE_NULL = 10034; + int PARAM_TYPE_NULL = 10035; + int PARAM_TYPE_INVALID = 10036; + int PARAM_NUMBER_INVALID = 10037; + int PARAM_BOOLEAN_INVALID = 10038; + int PARAM_ARRAY_INVALID = 10039; + int PARAM_JSON_INVALID = 10040; + + int OTA_DEVICE_NOT_FOUND = 10041; + int OTA_DEVICE_NEED_BIND = 10042; } diff --git a/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java b/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java index 0d0aa118..e8eaf462 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java +++ b/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java @@ -1,7 +1,13 @@ package xiaozhi.common.exception; +import java.util.List; +import java.util.Objects; + import org.apache.shiro.authz.UnauthorizedException; +import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.dao.DuplicateKeyException; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.resource.NoResourceFoundException; @@ -60,4 +66,20 @@ public class RenExceptionHandler { return new Result().error(404, "资源不存在"); } + @ExceptionHandler(MethodArgumentNotValidException.class) + public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { + List allErrors = ex.getBindingResult().getAllErrors(); + String errorMsg = allErrors.stream() + .filter(Objects::nonNull) + .map(err -> { + String msg = err.getDefaultMessage(); + return (msg != null && !msg.trim().isEmpty()) ? msg : null; + }) + .filter(Objects::nonNull) + .findFirst() + .orElse("请求参数错误!"); + + return new Result().error(ErrorCode.PARAM_VALUE_NULL, errorMsg); + } + } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java index 53aa08dc..6daa3b2d 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java +++ b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java @@ -38,7 +38,14 @@ public class RedisKeys { * 模型名称的Key */ public static String getModelNameById(String id) { - return "sys:model:name:" + id; + return "model:name:" + id; + } + + /** + * 模型配置的Key + */ + public static String getModelConfigById(String id) { + return "model:data:" + id; } /** @@ -54,4 +61,82 @@ public class RedisKeys { public static String getAgentDeviceCountById(String id) { return "agent:device:count:" + id; } + + /** + * 获取智能体最后连接时间缓存key + */ + public static String getAgentDeviceLastConnectedAtById(String id) { + return "agent:device:lastConnected:" + id; + } + + /** + * 获取系统配置缓存key + */ + public static String getServerConfigKey() { + return "server:config"; + } + + /** + * 获取音色详情缓存key + */ + public static String getTimbreDetailsKey(String id) { + return "timbre:details:" + id; + } + + /** + * 获取版本号Key + */ + public static String getVersionKey() { + return "sys:version"; + } + + /** + * OTA固件ID的Key + */ + public static String getOtaIdKey(String uuid) { + return "ota:id:" + uuid; + } + + /** + * OTA固件下载次数的Key + */ + public static String getOtaDownloadCountKey(String uuid) { + return "ota:download:count:" + uuid; + } + + /** + * 获取字典数据的缓存key + */ + public static String getDictDataByTypeKey(String dictType) { + return "sys:dict:data:" + dictType; + } + + /** + * 获取智能体音频ID的缓存key + */ + public static String getAgentAudioIdKey(String uuid) { + return "agent:audio:id:" + uuid; + } + + /** + * 获取短信验证码的缓存key + */ + public static String getSMSValidateCodeKey(String phone) { + return "sms:Validate:Code:" + phone; + } + + /** + * 获取短信验证码最后发送时间的缓存key + */ + public static String getSMSLastSendTimeKey(String phone) { + return "sms:Validate:Code:" + phone + ":last_send_time"; + } + + /** + * 获取短信验证码今日发送次数的缓存key + */ + public static String getSMSTodayCountKey(String phone) { + return "sms:Validate:Code:" + phone + ":today_count"; + } + } diff --git a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisUtils.java b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisUtils.java index 240e1339..c81677e9 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisUtils.java @@ -1,14 +1,19 @@ package xiaozhi.common.redis; import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Component; import jakarta.annotation.Resource; +import xiaozhi.common.utils.ResourcesUtils; /** * Redis工具类 @@ -20,6 +25,9 @@ public class RedisUtils { @Resource private RedisTemplate redisTemplate; + @Autowired + private ResourcesUtils resourceUtils; + /** * 默认过期时长为24小时,单位:秒 */ @@ -27,7 +35,7 @@ public class RedisUtils { /** * 过期时长为1小时,单位:秒 */ - public final static long HOUR_ONE_EXPIRE = 60 * 60 * 1L; + public final static long HOUR_ONE_EXPIRE = (long) 60 * 60; /** * 过期时长为6小时,单位:秒 */ @@ -37,6 +45,24 @@ public class RedisUtils { */ public final static long NOT_EXPIRE = -1L; + public Long increment(String key, long expire) { + Long increment = redisTemplate.opsForValue().increment(key, 1L); + if (expire != NOT_EXPIRE) { + expire(key, expire); + } + return increment; + } + + public Long increment(String key) { + return redisTemplate.opsForValue().increment(key, 1L); + } + + public Long decrement(String key) { + return redisTemplate.opsForValue().decrement(key, 1L); + } + + + public void set(String key, Object value, long expire) { redisTemplate.opsForValue().set(key, value); if (expire != NOT_EXPIRE) { @@ -124,4 +150,46 @@ public class RedisUtils { public Object rightPop(String key) { return redisTemplate.opsForList().rightPop(key); } + + + /** + * 清空所有 Redis 数据库中的所有键 + */ + public void emptyAll() { + // Lua 脚本 FLUSHALL是redis清空所有库的命令 + String luaScript =resourceUtils.loadString("lua/emptyAll.lua"); + + // 创建 DefaultRedisScript 对象 + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(luaScript); // 设置 Lua 脚本内容 + redisScript.setResultType(Void.class); // 设置返回值类型 + + // 执行 Lua 脚本 + List keys = Collections.emptyList(); // 如果脚本不依赖 key,可以传入空列表 + redisTemplate.execute(redisScript, keys); + + } + + /** + * 获取在redis指定key的值,如果值为空,着设置key的默认值 + * @param key redis的key + * @param defaultValue 默认值 + * @param expiresInSecond 过期时间 + * @return 返回key的值 + */ + public String getKeyOrCreate(String key, String defaultValue,Long expiresInSecond) { + // Lua 脚本 + String luaScript = resourceUtils.loadString("lua/getKeyOrCreate.lua"); + + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(luaScript); + redisScript.setResultType(String.class); + + // 执行 Lua 脚本 + List keys = Collections.singletonList(key); + return redisTemplate.execute(redisScript, keys, defaultValue,expiresInSecond); + } + + + } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/common/service/impl/BaseServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/common/service/impl/BaseServiceImpl.java index 8ebb4cd1..9d06c09a 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/service/impl/BaseServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/common/service/impl/BaseServiceImpl.java @@ -1,6 +1,7 @@ package xiaozhi.common.service.impl; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; @@ -18,6 +19,7 @@ import com.baomidou.mybatisplus.core.enums.SqlMethod; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.Constants; import com.baomidou.mybatisplus.core.toolkit.ReflectionKit; import com.baomidou.mybatisplus.core.toolkit.StringUtils; @@ -45,6 +47,12 @@ public abstract class BaseServiceImpl, T> implements Bas * @param params 分页查询参数 * @param defaultOrderField 默认排序字段 * @param isAsc 排序方式 + * @see xiaozhi.common.constant.Constant + * params.put(Constant.PAGE, "1"); + * params.put(Constant.LIMIT, "10"); + * params.put(Constant.ORDER_FIELD, "field"); // 单个字段 + * params.put(Constant.ORDER_FIELD, List.of("field1", "field2")); // 多个字段 + * params.put(Constant.ORDER, "asc"); */ protected IPage getPage(Map params, String defaultOrderField, boolean isAsc) { // 分页参数 @@ -65,28 +73,34 @@ public abstract class BaseServiceImpl, T> implements Bas params.put(Constant.PAGE, page); // 排序字段 - String orderField = (String) params.get(Constant.ORDER_FIELD); + Object orderField = params.get(Constant.ORDER_FIELD); String order = (String) params.get(Constant.ORDER); - // 前端字段排序 - if (StringUtils.isNotBlank(orderField) && StringUtils.isNotBlank(order)) { - if (Constant.ASC.equalsIgnoreCase(order)) { - return page.addOrder(OrderItem.asc(orderField)); + List orderFields = new ArrayList<>(); + + // 处理排序字段 + if (orderField instanceof String) { + orderFields.add((String) orderField); + } else if (orderField instanceof List) { + orderFields.addAll((List) orderField); + } + + // 有排序字段则排序 + if (CollectionUtils.isNotEmpty(orderFields)) { + if (StringUtils.isNotBlank(order) && Constant.ASC.equalsIgnoreCase(order)) { + return page.addOrder(OrderItem.ascs(orderFields.toArray(new String[0]))); } else { - return page.addOrder(OrderItem.desc(orderField)); + return page.addOrder(OrderItem.descs(orderFields.toArray(new String[0]))); } } - // 没有排序字段,则不排序 - if (StringUtils.isBlank(defaultOrderField)) { - return page; - } - - // 默认排序 - if (isAsc) { - page.addOrder(OrderItem.asc(defaultOrderField)); - } else { - page.addOrder(OrderItem.desc(defaultOrderField)); + // 没有排序字段,使用默认排序 + if (StringUtils.isNotBlank(defaultOrderField)) { + if (isAsc) { + page.addOrder(OrderItem.asc(defaultOrderField)); + } else { + page.addOrder(OrderItem.desc(defaultOrderField)); + } } return page; @@ -128,12 +142,10 @@ public abstract class BaseServiceImpl, T> implements Bas return SqlHelper.retBool(result); } - @SuppressWarnings("unchecked") protected Class currentMapperClass() { return (Class) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 0); } - @SuppressWarnings("unchecked") @Override public Class currentModelClass() { return (Class) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 1); diff --git a/main/manager-api/src/main/java/xiaozhi/common/service/impl/CrudServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/common/service/impl/CrudServiceImpl.java index 524a8cb2..9a1bf39e 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/service/impl/CrudServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/common/service/impl/CrudServiceImpl.java @@ -24,7 +24,6 @@ import xiaozhi.common.utils.ConvertUtils; public abstract class CrudServiceImpl, T, D> extends BaseServiceImpl implements CrudService { - @SuppressWarnings("unchecked") protected Class currentDtoClass() { return (Class) ReflectionKit.getSuperClassGenericType(getClass(), CrudServiceImpl.class, 2); } diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/DateUtils.java b/main/manager-api/src/main/java/xiaozhi/common/utils/DateUtils.java index bfa7afed..800733c1 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/utils/DateUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/DateUtils.java @@ -21,6 +21,8 @@ public class DateUtils { * 时间格式(yyyy-MM-dd HH:mm:ss) */ public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; + public final static String DATE_TIME_MILLIS_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS"; + /** * 日期格式化 日期格式为:yyyy-MM-dd @@ -63,6 +65,19 @@ public class DateUtils { return null; } + + public static String getDateTimeNow() { + return getDateTimeNow(DATE_TIME_PATTERN); + } + + public static String getDateTimeNow(String pattern) { + return format(new Date(), pattern); + } + + public static String millsToSecond(long mills) { + return String.format("%.3f", mills / 1000.0); + } + /** * 获取简短的时间字符串:10秒前返回刚刚,多少秒前,几小时前,超过一周返回年月日时分秒 * @param date diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/ResourcesUtils.java b/main/manager-api/src/main/java/xiaozhi/common/utils/ResourcesUtils.java new file mode 100644 index 00000000..49d9a727 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/ResourcesUtils.java @@ -0,0 +1,44 @@ +package xiaozhi.common.utils; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; +import xiaozhi.common.exception.RenException; + + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * 资源处理工具 + */ +@AllArgsConstructor +@Slf4j +@Component +public class ResourcesUtils { + private ResourceLoader resourceLoader; + + /** + * 读取资源,返回字符串 + * @param fileName 资源路径:resources下开始 + * @return 字符串 + */ + public String loadString(String fileName) { + Resource resource = resourceLoader.getResource("classpath:" + fileName); + StringBuilder luaScriptBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(resource.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + luaScriptBuilder.append(line).append("\n"); + } + } catch (IOException e){ + log.error("方法:loadString()读取资源失败--{}",e.getMessage()); + throw new RenException("读取资源失败"); + } + return luaScriptBuilder.toString(); + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/common/validator/ValidatorUtils.java b/main/manager-api/src/main/java/xiaozhi/common/validator/ValidatorUtils.java index 9e31c964..364da0d9 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/validator/ValidatorUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/validator/ValidatorUtils.java @@ -2,6 +2,7 @@ package xiaozhi.common.validator; import java.util.Locale; import java.util.Set; +import java.util.regex.Pattern; import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; import org.springframework.context.i18n.LocaleContextHolder; @@ -45,4 +46,32 @@ public class ValidatorUtils { throw new RenException(constraint.getMessage()); } } + + /** + * 国际手机号正则表达式 + * 要求必须带国际区号,格式:+[国家代码][手机号] + * 例如: + * - +8613800138000 + * - +12345678900 + * - +447123456789 + */ + private static final String INTERNATIONAL_PHONE_REGEX = "^\\+[1-9]\\d{0,3}[1-9]\\d{4,14}$"; + + /** + * 校验手机号是否有效 + * 要求必须带国际区号,格式:+[国家代码][手机号] + * 例如:+8613800138000 + * + * @param phone 手机号 + * @return boolean + */ + public static boolean isValidPhone(String phone) { + if (phone == null || phone.isEmpty()) { + return false; + } + + // 验证必须带国际区号的手机号格式 + Pattern pattern = Pattern.compile(INTERNATIONAL_PHONE_REGEX); + return pattern.matcher(phone).matches(); + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentChatHistoryController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentChatHistoryController.java new file mode 100644 index 00000000..9d6542cc --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentChatHistoryController.java @@ -0,0 +1,36 @@ +package xiaozhi.modules.agent.controller; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import xiaozhi.common.utils.Result; +import xiaozhi.modules.agent.dto.AgentChatHistoryReportDTO; +import xiaozhi.modules.agent.service.biz.AgentChatHistoryBizService; + +@Tag(name = "智能体聊天历史管理") +@RequiredArgsConstructor +@RestController +@RequestMapping("/agent/chat-history") +public class AgentChatHistoryController { + private final AgentChatHistoryBizService agentChatHistoryBizService; + + /** + * 小智服务聊天上报请求 + *

+ * 小智服务聊天上报请求,包含Base64编码的音频数据和相关信息。 + * + * @param request 包含上传文件及相关信息的请求对象 + */ + @Operation(summary = "小智服务聊天上报请求") + @PostMapping("/report") + public Result uploadFile(@Valid @RequestBody AgentChatHistoryReportDTO request) { + Boolean result = agentChatHistoryBizService.report(request); + return new Result().ok(result); + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java index eea2d7fe..54f3fa8c 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java @@ -3,8 +3,13 @@ package xiaozhi.modules.agent.controller; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.UUID; +import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -25,16 +30,24 @@ import jakarta.validation.Valid; import lombok.AllArgsConstructor; import xiaozhi.common.constant.Constant; import xiaozhi.common.page.PageData; +import xiaozhi.common.redis.RedisKeys; +import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.user.UserDetail; import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.Result; +import xiaozhi.modules.agent.dto.AgentChatHistoryDTO; +import xiaozhi.modules.agent.dto.AgentChatSessionDTO; import xiaozhi.modules.agent.dto.AgentCreateDTO; import xiaozhi.modules.agent.dto.AgentDTO; +import xiaozhi.modules.agent.dto.AgentMemoryDTO; import xiaozhi.modules.agent.dto.AgentUpdateDTO; import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentTemplateEntity; +import xiaozhi.modules.agent.service.AgentChatAudioService; +import xiaozhi.modules.agent.service.AgentChatHistoryService; import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.service.AgentTemplateService; +import xiaozhi.modules.device.entity.DeviceEntity; import xiaozhi.modules.device.service.DeviceService; import xiaozhi.modules.security.user.SecurityUser; @@ -46,6 +59,9 @@ public class AgentController { private final AgentService agentService; private final AgentTemplateService agentTemplateService; private final DeviceService deviceService; + private final AgentChatHistoryService agentChatHistoryService; + private final AgentChatAudioService agentChatAudioService; + private final RedisUtils redisUtils; @GetMapping("/list") @Operation(summary = "获取用户智能体列表") @@ -80,7 +96,7 @@ public class AgentController { @PostMapping @Operation(summary = "创建智能体") @RequiresPermissions("sys:role:normal") - public Result save(@RequestBody @Valid AgentCreateDTO dto) { + public Result save(@RequestBody @Valid AgentCreateDTO dto) { AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class); // 获取默认模板 @@ -95,6 +111,8 @@ public class AgentController { entity.setMemModelId(template.getMemModelId()); entity.setIntentModelId(template.getIntentModelId()); entity.setSystemPrompt(template.getSystemPrompt()); + entity.setSummaryMemory(template.getSummaryMemory()); + entity.setChatHistoryConf(template.getChatHistoryConf()); entity.setLangCode(template.getLangCode()); entity.setLanguage(template.getLanguage()); } @@ -108,13 +126,29 @@ public class AgentController { // ID、智能体编码和排序会在Service层自动生成 agentService.insert(entity); - return new Result<>(); + return new Result().ok(entity.getId()); + } + + @PutMapping("/saveMemory/{macAddress}") + @Operation(summary = "根据设备id更新智能体") + public Result updateByDeviceId(@PathVariable String macAddress, @RequestBody @Valid AgentMemoryDTO dto) { + DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress); + if (device == null) { + return new Result<>(); + } + AgentUpdateDTO agentUpdateDTO = new AgentUpdateDTO(); + agentUpdateDTO.setSummaryMemory(dto.getSummaryMemory()); + return updateAgentById(device.getAgentId(), agentUpdateDTO); } @PutMapping("/{id}") @Operation(summary = "更新智能体") @RequiresPermissions("sys:role:normal") public Result update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) { + return updateAgentById(id, dto); + } + + private Result updateAgentById(String id, AgentUpdateDTO dto) { // 先查询现有实体 AgentEntity existingEntity = agentService.getAgentById(id); if (existingEntity == null) { @@ -152,6 +186,12 @@ public class AgentController { if (dto.getSystemPrompt() != null) { existingEntity.setSystemPrompt(dto.getSystemPrompt()); } + if (dto.getSummaryMemory() != null) { + existingEntity.setSummaryMemory(dto.getSummaryMemory()); + } + if (dto.getChatHistoryConf() != null) { + existingEntity.setChatHistoryConf(dto.getChatHistoryConf()); + } if (dto.getLangCode() != null) { existingEntity.setLangCode(dto.getLangCode()); } @@ -167,8 +207,16 @@ public class AgentController { existingEntity.setUpdater(user.getId()); existingEntity.setUpdatedAt(new Date()); + // 更新记忆策略 + if (existingEntity.getMemModelId() == null || existingEntity.getMemModelId().equals(Constant.MEMORY_NO_MEM)) { + // 删除所有记录 + agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, true); + existingEntity.setSummaryMemory(""); + } else if (existingEntity.getChatHistoryConf() != null && existingEntity.getChatHistoryConf() == 1) { + // 删除音频数据 + agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, false); + } agentService.updateById(existingEntity); - return new Result<>(); } @@ -178,6 +226,8 @@ public class AgentController { public Result delete(@PathVariable String id) { // 先删除关联的设备 deviceService.deleteByAgentId(id); + // 删除关联的聊天记录 + agentChatHistoryService.deleteByAgentId(id, true, true); // 再删除智能体 agentService.deleteById(id); return new Result<>(); @@ -191,4 +241,72 @@ public class AgentController { .list(new QueryWrapper().orderByAsc("sort")); return new Result>().ok(list); } + + @GetMapping("/{id}/sessions") + @Operation(summary = "获取智能体会话列表") + @RequiresPermissions("sys:role:normal") + @Parameters({ + @Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true), + @Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true), + }) + public Result> getAgentSessions( + @PathVariable("id") String id, + @Parameter(hidden = true) @RequestParam Map params) { + params.put("agentId", id); + PageData page = agentChatHistoryService.getSessionListByAgentId(params); + return new Result>().ok(page); + } + + @GetMapping("/{id}/chat-history/{sessionId}") + @Operation(summary = "获取智能体聊天记录") + @RequiresPermissions("sys:role:normal") + public Result> getAgentChatHistory( + @PathVariable("id") String id, + @PathVariable("sessionId") String sessionId) { + // 获取当前用户 + UserDetail user = SecurityUser.getUser(); + + // 检查权限 + if (!agentService.checkAgentPermission(id, user.getId())) { + return new Result>().error("没有权限查看该智能体的聊天记录"); + } + + // 查询聊天记录 + List result = agentChatHistoryService.getChatHistoryBySessionId(id, sessionId); + return new Result>().ok(result); + } + + @PostMapping("/audio/{audioId}") + @Operation(summary = "获取音频下载ID") + @RequiresPermissions("sys:role:normal") + public Result getAudioId(@PathVariable("audioId") String audioId) { + byte[] audioData = agentChatAudioService.getAudio(audioId); + if (audioData == null) { + return new Result().error("音频不存在"); + } + String uuid = UUID.randomUUID().toString(); + redisUtils.set(RedisKeys.getAgentAudioIdKey(uuid), audioId); + return new Result().ok(uuid); + } + + @GetMapping("/play/{uuid}") + @Operation(summary = "播放音频") + public ResponseEntity playAudio(@PathVariable("uuid") String uuid) { + + String audioId = (String) redisUtils.get(RedisKeys.getAgentAudioIdKey(uuid)); + if (StringUtils.isBlank(audioId)) { + return ResponseEntity.notFound().build(); + } + + byte[] audioData = agentChatAudioService.getAudio(audioId); + if (audioData == null) { + return ResponseEntity.notFound().build(); + } + redisUtils.delete(RedisKeys.getAgentAudioIdKey(uuid)); + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"play.wav\"") + .body(audioData); + } + } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java index 8c80d6cc..3b4be452 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java @@ -3,6 +3,7 @@ package xiaozhi.modules.agent.dao; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; import xiaozhi.common.dao.BaseDao; import xiaozhi.modules.agent.entity.AgentEntity; @@ -15,4 +16,16 @@ public interface AgentDao extends BaseDao { * @return 设备数量 */ Integer getDeviceCountByAgentId(@Param("agentId") String agentId); -} \ No newline at end of file + + /** + * 根据设备MAC地址查询对应设备的默认智能体信息 + * + * @param macAddress 设备MAC地址 + * @return 默认智能体信息 + */ + @Select(" SELECT a.* FROM ai_device d " + + " LEFT JOIN ai_agent a ON d.agent_id = a.id " + + " WHERE d.mac_address = #{macAddress} " + + " ORDER BY d.id DESC LIMIT 1") + AgentEntity getDefaultAgentByMacAddress(@Param("macAddress") String macAddress); +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AiAgentChatAudioDao.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AiAgentChatAudioDao.java new file mode 100644 index 00000000..355b25ac --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AiAgentChatAudioDao.java @@ -0,0 +1,18 @@ +package xiaozhi.modules.agent.dao; + +import org.apache.ibatis.annotations.Mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import xiaozhi.modules.agent.entity.AgentChatAudioEntity; + +/** + * {@link AgentChatAudioEntity} 智能体聊天音频数据Dao对象 + * + * @author Goody + * @version 1.0, 2025/5/8 + * @since 1.0.0 + */ +@Mapper +public interface AiAgentChatAudioDao extends BaseMapper { +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AiAgentChatHistoryDao.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AiAgentChatHistoryDao.java new file mode 100644 index 00000000..b75312d2 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AiAgentChatHistoryDao.java @@ -0,0 +1,38 @@ +package xiaozhi.modules.agent.dao; + +import org.apache.ibatis.annotations.Mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; + +/** + * {@link AgentChatHistoryEntity} 智能体聊天历史记录Dao对象 + * + * @author Goody + * @version 1.0, 2025/4/30 + * @since 1.0.0 + */ +@Mapper +public interface AiAgentChatHistoryDao extends BaseMapper { + /** + * 根据智能体ID删除音频 + * + * @param agentId 智能体ID + */ + void deleteAudioByAgentId(String agentId); + + /** + * 根据智能体ID删除聊天历史记录 + * + * @param agentId 智能体ID + */ + void deleteHistoryByAgentId(String agentId); + + /** + * 根据智能体ID删除音频ID + * + * @param agentId 智能体ID + */ + void deleteAudioIdByAgentId(String agentId); +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatHistoryDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatHistoryDTO.java new file mode 100644 index 00000000..fd49e52b --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatHistoryDTO.java @@ -0,0 +1,28 @@ +package xiaozhi.modules.agent.dto; + +import java.util.Date; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 智能体聊天记录DTO + */ +@Data +@Schema(description = "智能体聊天记录") +public class AgentChatHistoryDTO { + @Schema(description = "创建时间") + private Date createdAt; + + @Schema(description = "消息类型: 1-用户, 2-智能体") + private Byte chatType; + + @Schema(description = "聊天内容") + private String content; + + @Schema(description = "音频ID") + private String audioId; + + @Schema(description = "MAC地址") + private String macAddress; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatHistoryReportDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatHistoryReportDTO.java new file mode 100644 index 00000000..bdaed950 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatHistoryReportDTO.java @@ -0,0 +1,31 @@ +package xiaozhi.modules.agent.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 小智设备聊天上报请求 + * + * @author Haotian + * @version 1.0, 2025/5/8 + */ +@Data +@Schema(description = "小智设备聊天上报请求") +public class AgentChatHistoryReportDTO { + @Schema(description = "MAC地址", example = "00:11:22:33:44:55") + @NotBlank + private String macAddress; + @Schema(description = "会话ID", example = "79578c31-f1fb-426a-900e-1e934215f05a") + @NotBlank + private String sessionId; + @Schema(description = "消息类型: 1-用户, 2-智能体", example = "1") + @NotNull + private Byte chatType; + @Schema(description = "聊天内容", example = "你好呀") + @NotBlank + private String content; + @Schema(description = "base64编码的opus音频数据", example = "") + private String audioBase64; +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSessionDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSessionDTO.java new file mode 100644 index 00000000..c65f05c6 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSessionDTO.java @@ -0,0 +1,26 @@ +package xiaozhi.modules.agent.dto; + +import java.time.LocalDateTime; + +import lombok.Data; + +/** + * 智能体会话列表DTO + */ +@Data +public class AgentChatSessionDTO { + /** + * 会话ID + */ + private String sessionId; + + /** + * 会话时间 + */ + private LocalDateTime createdAt; + + /** + * 聊天条数 + */ + private Integer chatCount; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java index a63c96e7..075db842 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java @@ -27,9 +27,16 @@ public class AgentDTO { @Schema(description = "大语言模型名称", example = "llm_model_01") private String llmModelName; + @Schema(description = "记忆模型ID", example = "mem_model_01") + private String memModelId; + @Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助") private String systemPrompt; + @Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" + + "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false) + private String summaryMemory; + @Schema(description = "最后连接时间", example = "2024-03-20 10:00:00") private Date lastConnectedAt; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentMemoryDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentMemoryDTO.java new file mode 100644 index 00000000..ed210c60 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentMemoryDTO.java @@ -0,0 +1,19 @@ +package xiaozhi.modules.agent.dto; + +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 智能体记忆更新DTO + */ +@Data +@Schema(description = "智能体记忆更新对象") +public class AgentMemoryDTO implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" + + "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false) + private String summaryMemory; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java index d54685d8..ccfbd53f 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java @@ -45,6 +45,13 @@ public class AgentUpdateDTO implements Serializable { @Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", required = false) private String systemPrompt; + @Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" + + "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false) + private String summaryMemory; + + @Schema(description = "聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)", example = "3", required = false) + private Integer chatHistoryConf; + @Schema(description = "语言编码", example = "zh_CN", required = false) private String langCode; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatAudioEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatAudioEntity.java new file mode 100644 index 00000000..ac30d9d4 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatAudioEntity.java @@ -0,0 +1,29 @@ +package xiaozhi.modules.agent.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import lombok.Data; + +/** + * 智能体聊天音频数据表 + * + * @author Goody + * @version 1.0, 2025/5/8 + * @since 1.0.0 + */ +@Data +@TableName("ai_agent_chat_audio") +public class AgentChatAudioEntity { + /** + * 主键ID + */ + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 音频opus数据 + */ + private byte[] audio; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatHistoryEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatHistoryEntity.java new file mode 100644 index 00000000..679c814a --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentChatHistoryEntity.java @@ -0,0 +1,81 @@ +package xiaozhi.modules.agent.entity; + +import java.util.Date; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 智能体聊天记录表 + * + * @author Goody + * @version 1.0, 2025/4/30 + * @since 1.0.0 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@TableName(value = "ai_agent_chat_history") +public class AgentChatHistoryEntity { + /** + * 主键ID + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * MAC地址 + */ + @TableField(value = "mac_address") + private String macAddress; + + /** + * 智能体id + */ + @TableField(value = "agent_id") + private String agentId; + + /** + * 会话ID + */ + @TableField(value = "session_id") + private String sessionId; + + /** + * 消息类型: 1-用户, 2-智能体 + */ + @TableField(value = "chat_type") + private Byte chatType; + + /** + * 聊天内容 + */ + @TableField(value = "content") + private String content; + + /** + * 音频base64数据 + */ + @TableField(value = "audio_id") + private String audioId; + + /** + * 创建时间 + */ + @TableField(value = "created_at") + private Date createdAt; + + /** + * 更新时间 + */ + @TableField(value = "updated_at") + private Date updatedAt; +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java index 917fe6d2..1ffc5550 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java @@ -48,9 +48,16 @@ public class AgentEntity { @Schema(description = "意图模型标识") private String intentModelId; + @Schema(description = "聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)") + private Integer chatHistoryConf; + @Schema(description = "角色设定参数") private String systemPrompt; + @Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" + + "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false) + private String summaryMemory; + @Schema(description = "语言编码") private String langCode; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentTemplateEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentTemplateEntity.java index 6d83e4fe..2528ca2c 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentTemplateEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentTemplateEntity.java @@ -69,11 +69,20 @@ public class AgentTemplateEntity implements Serializable { */ private String intentModelId; + /** + * 聊天记录配置(0不记录 1仅记录文本 2记录文本和语音) + */ + private Integer chatHistoryConf; + /** * 角色设定参数 */ private String systemPrompt; + /** + * 总结记忆 + */ + private String summaryMemory; /** * 语言编码 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatAudioService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatAudioService.java new file mode 100644 index 00000000..e284b69f --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatAudioService.java @@ -0,0 +1,30 @@ +package xiaozhi.modules.agent.service; + +import com.baomidou.mybatisplus.extension.service.IService; + +import xiaozhi.modules.agent.entity.AgentChatAudioEntity; + +/** + * 智能体聊天音频数据表处理service + * + * @author Goody + * @version 1.0, 2025/5/8 + * @since 1.0.0 + */ +public interface AgentChatAudioService extends IService { + /** + * 保存音频数据 + * + * @param audioData 音频数据 + * @return 音频ID + */ + String saveAudio(byte[] audioData); + + /** + * 获取音频数据 + * + * @param audioId 音频ID + * @return 音频数据 + */ + byte[] getAudio(String audioId); +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatHistoryService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatHistoryService.java new file mode 100644 index 00000000..21fce988 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatHistoryService.java @@ -0,0 +1,47 @@ +package xiaozhi.modules.agent.service; + +import java.util.List; +import java.util.Map; + +import com.baomidou.mybatisplus.extension.service.IService; + +import xiaozhi.common.page.PageData; +import xiaozhi.modules.agent.dto.AgentChatHistoryDTO; +import xiaozhi.modules.agent.dto.AgentChatSessionDTO; +import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; + +/** + * 智能体聊天记录表处理service + * + * @author Goody + * @version 1.0, 2025/4/30 + * @since 1.0.0 + */ +public interface AgentChatHistoryService extends IService { + + /** + * 根据智能体ID获取会话列表 + * + * @param params 查询参数,包含agentId、page、limit + * @return 分页的会话列表 + */ + PageData getSessionListByAgentId(Map params); + + /** + * 根据会话ID获取聊天记录列表 + * + * @param agentId 智能体ID + * @param sessionId 会话ID + * @return 聊天记录列表 + */ + List getChatHistoryBySessionId(String agentId, String sessionId); + + /** + * 根据智能体ID删除聊天记录 + * + * @param agentId 智能体ID + * @param deleteAudio 是否删除音频 + * @param deleteText 是否删除文本 + */ + void deleteByAgentId(String agentId, Boolean deleteAudio, Boolean deleteText); +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java index 8d0b2959..2104d353 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java @@ -8,38 +8,75 @@ import xiaozhi.common.service.BaseService; import xiaozhi.modules.agent.dto.AgentDTO; import xiaozhi.modules.agent.entity.AgentEntity; +/** + * 智能体表处理service + * + * @author Goody + * @version 1.0, 2025/4/30 + * @since 1.0.0 + */ public interface AgentService extends BaseService { - /** - * 管理员获取所有智能体列表(分页) + * 获取管理员智能体列表 + * + * @param params 查询参数 + * @return 分页数据 */ PageData adminAgentList(Map params); /** - * 获取智能体详情 + * 根据ID获取智能体 + * + * @param id 智能体ID + * @return 智能体实体 */ AgentEntity getAgentById(String id); /** - * 删除这个用户的所有 - * - * @param userId + * 插入智能体 + * + * @param entity 智能体实体 + * @return 是否成功 + */ + boolean insert(AgentEntity entity); + + /** + * 根据用户ID删除智能体 + * + * @param userId 用户ID */ void deleteAgentByUserId(Long userId); /** * 获取用户智能体列表 - * - * @param userId - * @return + * + * @param userId 用户ID + * @return 智能体列表 */ List getUserAgents(Long userId); /** - * 获取智能体的设备数量 - * + * 根据智能体ID获取设备数量 + * * @param agentId 智能体ID * @return 设备数量 */ Integer getDeviceCountByAgentId(String agentId); -} \ No newline at end of file + + /** + * 根据设备MAC地址查询对应设备的默认智能体信息 + * + * @param macAddress 设备MAC地址 + * @return 默认智能体信息,不存在时返回null + */ + AgentEntity getDefaultAgentByMacAddress(String macAddress); + + /** + * 检查用户是否有权限访问智能体 + * + * @param agentId 智能体ID + * @param userId 用户ID + * @return 是否有权限 + */ + boolean checkAgentPermission(String agentId, Long userId); +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentTemplateService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentTemplateService.java index 197c860e..4e279c40 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentTemplateService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentTemplateService.java @@ -17,4 +17,12 @@ public interface AgentTemplateService extends IService { * @return 默认模板实体 */ AgentTemplateEntity getDefaultTemplate(); + + /** + * 更新默认模板中的模型ID + * + * @param modelType 模型类型 + * @param modelId 模型ID + */ + void updateDefaultTemplateModelId(String modelType, String modelId); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/AgentChatHistoryBizService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/AgentChatHistoryBizService.java new file mode 100644 index 00000000..191b231b --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/AgentChatHistoryBizService.java @@ -0,0 +1,22 @@ +package xiaozhi.modules.agent.service.biz; + +import xiaozhi.modules.agent.dto.AgentChatHistoryReportDTO; + +/** + * 智能体聊天历史业务逻辑层 + * + * @author Goody + * @version 1.0, 2025/4/30 + * @since 1.0.0 + */ +public interface AgentChatHistoryBizService { + + /** + * 聊天上报方法 + * + * @param agentChatHistoryReportDTO 包含聊天上报所需信息的输入对象 + * 例如:设备MAC地址、文件类型、内容等 + * @return 上传结果,true表示成功,false表示失败 + */ + Boolean report(AgentChatHistoryReportDTO agentChatHistoryReportDTO); +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java new file mode 100644 index 00000000..bf6c188b --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java @@ -0,0 +1,112 @@ +package xiaozhi.modules.agent.service.biz.impl; + +import java.util.Base64; +import java.util.Date; +import java.util.Objects; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import xiaozhi.common.constant.Constant; +import xiaozhi.common.redis.RedisKeys; +import xiaozhi.common.redis.RedisUtils; +import xiaozhi.modules.agent.dto.AgentChatHistoryReportDTO; +import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; +import xiaozhi.modules.agent.entity.AgentEntity; +import xiaozhi.modules.agent.service.AgentChatAudioService; +import xiaozhi.modules.agent.service.AgentChatHistoryService; +import xiaozhi.modules.agent.service.AgentService; +import xiaozhi.modules.agent.service.biz.AgentChatHistoryBizService; + +/** + * {@link AgentChatHistoryBizService} impl + * + * @author Goody + * @version 1.0, 2025/4/30 + * @since 1.0.0 + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizService { + private final AgentService agentService; + private final AgentChatHistoryService agentChatHistoryService; + private final AgentChatAudioService agentChatAudioService; + private final RedisUtils redisUtils; + + /** + * 处理聊天记录上报,包括文件上传和相关信息记录 + * + * @param report 包含聊天上报所需信息的输入对象 + * @return 上传结果,true表示成功,false表示失败 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean report(AgentChatHistoryReportDTO report) { + String macAddress = report.getMacAddress(); + Byte chatType = report.getChatType(); + log.info("小智设备聊天上报请求: macAddress={}, type={}", macAddress, chatType); + + // 根据设备MAC地址查询对应的默认智能体,判断是否需要上报 + AgentEntity agentEntity = agentService.getDefaultAgentByMacAddress(macAddress); + if (agentEntity == null) { + return Boolean.FALSE; + } + + Integer chatHistoryConf = agentEntity.getChatHistoryConf(); + String agentId = agentEntity.getId(); + + if (Objects.equals(chatHistoryConf, Constant.ChatHistoryConfEnum.RECORD_TEXT.getCode())) { + saveChatText(report, agentId, macAddress, null); + } else if (Objects.equals(chatHistoryConf, Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode())) { + String audioId = saveChatAudio(report); + saveChatText(report, agentId, macAddress, audioId); + } + + // 更新设备最后对话时间 + redisUtils.set(RedisKeys.getAgentDeviceLastConnectedAtById(agentId), new Date()); + return Boolean.TRUE; + } + + /** + * base64解码report.getOpusDataBase64(),存入ai_agent_chat_audio表 + */ + private String saveChatAudio(AgentChatHistoryReportDTO report) { + String audioId = null; + + if (report.getAudioBase64() != null && !report.getAudioBase64().isEmpty()) { + try { + byte[] audioData = Base64.getDecoder().decode(report.getAudioBase64()); + audioId = agentChatAudioService.saveAudio(audioData); + log.info("音频数据保存成功,audioId={}", audioId); + } catch (Exception e) { + log.error("音频数据保存失败", e); + return null; + } + } + return audioId; + } + + /** + * 组装上报数据 + */ + private void saveChatText(AgentChatHistoryReportDTO report, String agentId, String macAddress, String audioId) { + + // 构建聊天记录实体 + AgentChatHistoryEntity entity = AgentChatHistoryEntity.builder() + .macAddress(macAddress) + .agentId(agentId) + .sessionId(report.getSessionId()) + .chatType(report.getChatType()) + .content(report.getContent()) + .audioId(audioId) + .build(); + + // 保存数据 + agentChatHistoryService.save(entity); + + log.info("设备 {} 对应智能体 {} 上报成功", macAddress, agentId); + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatAudioServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatAudioServiceImpl.java new file mode 100644 index 00000000..cf804678 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatAudioServiceImpl.java @@ -0,0 +1,34 @@ +package xiaozhi.modules.agent.service.impl; + +import org.springframework.stereotype.Service; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import xiaozhi.modules.agent.dao.AiAgentChatAudioDao; +import xiaozhi.modules.agent.entity.AgentChatAudioEntity; +import xiaozhi.modules.agent.service.AgentChatAudioService; + +/** + * 智能体聊天音频数据表处理service {@link AgentChatAudioService} impl + * + * @author Goody + * @version 1.0, 2025/5/8 + * @since 1.0.0 + */ +@Service +public class AgentChatAudioServiceImpl extends ServiceImpl + implements AgentChatAudioService { + @Override + public String saveAudio(byte[] audioData) { + AgentChatAudioEntity entity = new AgentChatAudioEntity(); + entity.setAudio(audioData); + save(entity); + return entity.getId(); + } + + @Override + public byte[] getAudio(String audioId) { + AgentChatAudioEntity entity = getById(audioId); + return entity != null ? entity.getAudio() : null; + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatHistoryServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatHistoryServiceImpl.java new file mode 100644 index 00000000..b28e80ad --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatHistoryServiceImpl.java @@ -0,0 +1,93 @@ +package xiaozhi.modules.agent.service.impl; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import xiaozhi.common.constant.Constant; +import xiaozhi.common.page.PageData; +import xiaozhi.common.utils.ConvertUtils; +import xiaozhi.modules.agent.dao.AiAgentChatHistoryDao; +import xiaozhi.modules.agent.dto.AgentChatHistoryDTO; +import xiaozhi.modules.agent.dto.AgentChatSessionDTO; +import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; +import xiaozhi.modules.agent.service.AgentChatHistoryService; + +/** + * 智能体聊天记录表处理service {@link AgentChatHistoryService} impl + * + * @author Goody + * @version 1.0, 2025/4/30 + * @since 1.0.0 + */ +@Service +public class AgentChatHistoryServiceImpl extends ServiceImpl + implements AgentChatHistoryService { + + @Override + public PageData getSessionListByAgentId(Map params) { + String agentId = (String) params.get("agentId"); + int page = Integer.parseInt(params.get(Constant.PAGE).toString()); + int limit = Integer.parseInt(params.get(Constant.LIMIT).toString()); + + // 构建查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.select("session_id", "MAX(created_at) as created_at", "COUNT(*) as chat_count") + .eq("agent_id", agentId) + .groupBy("session_id") + .orderByDesc("created_at"); + + // 执行分页查询 + Page> pageParam = new Page<>(page, limit); + IPage> result = this.baseMapper.selectMapsPage(pageParam, wrapper); + + List records = result.getRecords().stream().map(map -> { + AgentChatSessionDTO dto = new AgentChatSessionDTO(); + dto.setSessionId((String) map.get("session_id")); + dto.setCreatedAt((LocalDateTime) map.get("created_at")); + dto.setChatCount(((Number) map.get("chat_count")).intValue()); + return dto; + }).collect(Collectors.toList()); + + return new PageData<>(records, result.getTotal()); + } + + @Override + public List getChatHistoryBySessionId(String agentId, String sessionId) { + // 构建查询条件 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("agent_id", agentId) + .eq("session_id", sessionId) + .orderByAsc("created_at"); + + // 查询聊天记录 + List historyList = list(wrapper); + + // 转换为DTO + return ConvertUtils.sourceToTarget(historyList, AgentChatHistoryDTO.class); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteByAgentId(String agentId, Boolean deleteAudio, Boolean deleteText) { + if (deleteAudio) { + baseMapper.deleteAudioByAgentId(agentId); + } + if (deleteAudio && !deleteText) { + baseMapper.deleteAudioIdByAgentId(agentId); + } + if (deleteText) { + baseMapper.deleteHistoryByAgentId(agentId); + } + + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java index bb2a3ed0..37bb7d93 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java @@ -6,13 +6,14 @@ import java.util.UUID; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.AllArgsConstructor; +import xiaozhi.common.constant.Constant; import xiaozhi.common.page.PageData; import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; @@ -21,37 +22,40 @@ import xiaozhi.modules.agent.dao.AgentDao; import xiaozhi.modules.agent.dto.AgentDTO; import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.service.AgentService; +import xiaozhi.modules.device.service.DeviceService; import xiaozhi.modules.model.service.ModelConfigService; +import xiaozhi.modules.security.user.SecurityUser; +import xiaozhi.modules.sys.enums.SuperAdminEnum; import xiaozhi.modules.timbre.service.TimbreService; @Service +@AllArgsConstructor public class AgentServiceImpl extends BaseServiceImpl implements AgentService { private final AgentDao agentDao; - - @Autowired - private TimbreService timbreModelService; - - @Autowired - private ModelConfigService modelConfigService; - - @Autowired - private RedisUtils redisUtils; - - public AgentServiceImpl(AgentDao agentDao) { - this.agentDao = agentDao; - } + private final TimbreService timbreModelService; + private final ModelConfigService modelConfigService; + private final RedisUtils redisUtils; + private final DeviceService deviceService; @Override public PageData adminAgentList(Map params) { IPage page = agentDao.selectPage( - getPage(params, "sort", true), + getPage(params, "agent_name", true), new QueryWrapper<>()); return new PageData<>(page.getRecords(), page.getTotal()); } @Override public AgentEntity getAgentById(String id) { - return agentDao.selectById(id); + AgentEntity agent = agentDao.selectById(id); + if (agent != null && agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) { + agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode()); + } else if (agent != null && agent.getMemModelId() != null + && !agent.getMemModelId().equals(Constant.MEMORY_NO_MEM) + && agent.getChatHistoryConf() == null) { + agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode()); + } + return agent; } @Override @@ -98,12 +102,17 @@ public class AgentServiceImpl extends BaseServiceImpl imp // 获取 LLM 模型名称 dto.setLlmModelName(modelConfigService.getModelNameById(agent.getLlmModelId())); + // 获取记忆模型名称 + dto.setMemModelId(agent.getMemModelId()); + // 获取 TTS 音色名称 dto.setTtsVoiceName(timbreModelService.getTimbreNameById(agent.getTtsVoiceId())); + // 获取智能体最近的最后连接时长 + dto.setLastConnectedAt(deviceService.getLatestLastConnectionTime(agent.getId())); + // 获取设备数量 dto.setDeviceCount(getDeviceCountByAgentId(agent.getId())); - return dto; }).collect(Collectors.toList()); } @@ -130,4 +139,29 @@ public class AgentServiceImpl extends BaseServiceImpl imp return deviceCount != null ? deviceCount : 0; } -} \ No newline at end of file + + @Override + public AgentEntity getDefaultAgentByMacAddress(String macAddress) { + if (StringUtils.isEmpty(macAddress)) { + return null; + } + return agentDao.getDefaultAgentByMacAddress(macAddress); + } + + @Override + public boolean checkAgentPermission(String agentId, Long userId) { + // 获取智能体信息 + AgentEntity agent = getAgentById(agentId); + if (agent == null) { + return false; + } + + // 如果是超级管理员,直接返回true + if (SecurityUser.getUser().getSuperAdmin() == SuperAdminEnum.YES.value()) { + return true; + } + + // 检查是否是智能体的所有者 + return userId.equals(agent.getUserId()); + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentTemplateServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentTemplateServiceImpl.java index 949a2f2d..9ac3de25 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentTemplateServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentTemplateServiceImpl.java @@ -3,6 +3,7 @@ package xiaozhi.modules.agent.service.impl; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import xiaozhi.modules.agent.dao.AgentTemplateDao; @@ -29,4 +30,40 @@ public class AgentTemplateServiceImpl extends ServiceImpl wrapper = new UpdateWrapper<>(); + switch (modelType) { + case "ASR": + wrapper.set("asr_model_id", modelId); + break; + case "VAD": + wrapper.set("vad_model_id", modelId); + break; + case "LLM": + wrapper.set("llm_model_id", modelId); + break; + case "TTS": + wrapper.set("tts_model_id", modelId); + wrapper.set("tts_voice_id", null); + break; + case "MEMORY": + wrapper.set("mem_model_id", modelId); + break; + case "INTENT": + wrapper.set("intent_model_id", modelId); + break; + } + wrapper.ge("sort", 0); + update(wrapper); + } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/config/controller/ConfigController.java b/main/manager-api/src/main/java/xiaozhi/modules/config/controller/ConfigController.java new file mode 100644 index 00000000..f83efb3a --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/config/controller/ConfigController.java @@ -0,0 +1,44 @@ +package xiaozhi.modules.config.controller; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import xiaozhi.common.utils.Result; +import xiaozhi.common.validator.ValidatorUtils; +import xiaozhi.modules.config.dto.AgentModelsDTO; +import xiaozhi.modules.config.service.ConfigService; + +/** + * xiaozhi-server 配置获取 + * + * @since 1.0.0 + */ +@RestController +@RequestMapping("config") +@Tag(name = "参数管理") +@AllArgsConstructor +public class ConfigController { + private final ConfigService configService; + + @PostMapping("server-base") + @Operation(summary = "获取配置") + public Result getConfig() { + Object config = configService.getConfig(true); + return new Result().ok(config); + } + + @PostMapping("agent-models") + @Operation(summary = "获取智能体模型") + public Result getAgentModels(@Valid @RequestBody AgentModelsDTO dto) { + // 效验数据 + ValidatorUtils.validateEntity(dto); + Object models = configService.getAgentModels(dto.getMacAddress(), dto.getSelectedModule()); + return new Result().ok(models); + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/config/dto/AgentModelsDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/config/dto/AgentModelsDTO.java new file mode 100644 index 00000000..47c08a81 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/config/dto/AgentModelsDTO.java @@ -0,0 +1,25 @@ +package xiaozhi.modules.config.dto; + +import java.util.Map; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +@Schema(description = "获取智能体模型配置DTO") +public class AgentModelsDTO { + + @NotBlank(message = "设备MAC地址不能为空") + @Schema(description = "设备MAC地址") + private String macAddress; + + @NotBlank(message = "客户端ID不能为空") + @Schema(description = "客户端ID") + private String clientId; + + @NotNull(message = "客户端已实例化的模型不能为空") + @Schema(description = "客户端已实例化的模型") + private Map selectedModule; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/config/init/SystemInitConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/config/init/SystemInitConfig.java new file mode 100644 index 00000000..e47cd0c7 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/config/init/SystemInitConfig.java @@ -0,0 +1,41 @@ +package xiaozhi.modules.config.init; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; + +import jakarta.annotation.PostConstruct; +import xiaozhi.common.constant.Constant; +import xiaozhi.common.redis.RedisKeys; +import xiaozhi.common.redis.RedisUtils; +import xiaozhi.modules.config.service.ConfigService; +import xiaozhi.modules.sys.service.SysParamsService; + +@Configuration +@DependsOn("liquibase") +public class SystemInitConfig { + + @Autowired + private SysParamsService sysParamsService; + + @Autowired + private ConfigService configService; + + @Autowired + private RedisUtils redisUtils; + + @PostConstruct + public void init() { + // 检查版本号 + String redisVersion = (String) redisUtils.get(RedisKeys.getVersionKey()); + if (!Constant.VERSION.equals(redisVersion)) { + // 如果版本不一致,清空Redis + redisUtils.emptyAll(); + // 存储新版本号 + redisUtils.set(RedisKeys.getVersionKey(), Constant.VERSION); + } + + sysParamsService.initServerSecret(); + configService.getConfig(false); + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/config/service/ConfigService.java b/main/manager-api/src/main/java/xiaozhi/modules/config/service/ConfigService.java new file mode 100644 index 00000000..93845e8e --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/config/service/ConfigService.java @@ -0,0 +1,22 @@ +package xiaozhi.modules.config.service; + +import java.util.Map; + +public interface ConfigService { + /** + * 获取服务器配置 + * + * @param isCache 是否缓存 + * @return 配置信息 + */ + Object getConfig(Boolean isCache); + + /** + * 获取智能体模型配置 + * + * @param macAddress MAC地址 + * @param selectedModule 客户端已实例化的模型 + * @return 模型配置信息 + */ + Map getAgentModels(String macAddress, Map selectedModule); +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java new file mode 100644 index 00000000..592b3c1d --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java @@ -0,0 +1,302 @@ +package xiaozhi.modules.config.service.impl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import lombok.AllArgsConstructor; +import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.ErrorCode; +import xiaozhi.common.exception.RenException; +import xiaozhi.common.redis.RedisKeys; +import xiaozhi.common.redis.RedisUtils; +import xiaozhi.common.utils.JsonUtils; +import xiaozhi.modules.agent.entity.AgentEntity; +import xiaozhi.modules.agent.entity.AgentTemplateEntity; +import xiaozhi.modules.agent.service.AgentService; +import xiaozhi.modules.agent.service.AgentTemplateService; +import xiaozhi.modules.config.service.ConfigService; +import xiaozhi.modules.device.entity.DeviceEntity; +import xiaozhi.modules.device.service.DeviceService; +import xiaozhi.modules.model.entity.ModelConfigEntity; +import xiaozhi.modules.model.service.ModelConfigService; +import xiaozhi.modules.sys.dto.SysParamsDTO; +import xiaozhi.modules.sys.service.SysParamsService; +import xiaozhi.modules.timbre.service.TimbreService; +import xiaozhi.modules.timbre.vo.TimbreDetailsVO; + +@Service +@AllArgsConstructor +public class ConfigServiceImpl implements ConfigService { + private final SysParamsService sysParamsService; + private final DeviceService deviceService; + private final ModelConfigService modelConfigService; + private final AgentService agentService; + private final AgentTemplateService agentTemplateService; + private final RedisUtils redisUtils; + private final TimbreService timbreService; + + @Override + public Object getConfig(Boolean isCache) { + if (isCache) { + // 先从Redis获取配置 + Object cachedConfig = redisUtils.get(RedisKeys.getServerConfigKey()); + if (cachedConfig != null) { + return cachedConfig; + } + } + + // 构建配置信息 + Map result = new HashMap<>(); + buildConfig(result); + + // 查询默认智能体 + AgentTemplateEntity agent = agentTemplateService.getDefaultTemplate(); + if (agent == null) { + throw new RenException("默认智能体未找到"); + } + + // 构建模块配置 + buildModuleConfig( + null, + null, + null, + null, + agent.getVadModelId(), + agent.getAsrModelId(), + null, + null, + null, + null, + result, + isCache); + + // 将配置存入Redis + redisUtils.set(RedisKeys.getServerConfigKey(), result); + + return result; + } + + @Override + public Map getAgentModels(String macAddress, Map selectedModule) { + // 根据MAC地址查找设备 + DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress); + if (device == null) { + // 如果设备,去redis里看看有没有需要连接的设备 + String cachedCode = deviceService.geCodeByDeviceId(macAddress); + if (StringUtils.isNotBlank(cachedCode)) { + throw new RenException(ErrorCode.OTA_DEVICE_NEED_BIND, cachedCode); + } + throw new RenException(ErrorCode.OTA_DEVICE_NOT_FOUND, "not found device"); + } + + // 获取智能体信息 + AgentEntity agent = agentService.getAgentById(device.getAgentId()); + if (agent == null) { + throw new RenException("智能体未找到"); + } + // 获取音色信息 + String voice = null; + TimbreDetailsVO timbre = timbreService.get(agent.getTtsVoiceId()); + if (timbre != null) { + voice = timbre.getTtsVoice(); + } + // 构建返回数据 + Map result = new HashMap<>(); + // 获取单台设备每天最多输出字数 + String deviceMaxOutputSize = sysParamsService.getValue("device_max_output_size", true); + result.put("device_max_output_size", deviceMaxOutputSize); + + // 获取聊天记录配置 + Integer chatHistoryConf = agent.getChatHistoryConf(); + if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) { + chatHistoryConf = Constant.ChatHistoryConfEnum.IGNORE.getCode(); + } else if (agent.getMemModelId() != null + && !agent.getMemModelId().equals(Constant.MEMORY_NO_MEM) + && agent.getChatHistoryConf() == null) { + chatHistoryConf = Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode(); + } + result.put("chat_history_conf", chatHistoryConf); + // 如果客户端已实例化模型,则不返回 + String alreadySelectedVadModelId = (String) selectedModule.get("VAD"); + if (alreadySelectedVadModelId != null && alreadySelectedVadModelId.equals(agent.getVadModelId())) { + agent.setVadModelId(null); + } + String alreadySelectedAsrModelId = (String) selectedModule.get("ASR"); + if (alreadySelectedAsrModelId != null && alreadySelectedAsrModelId.equals(agent.getAsrModelId())) { + agent.setAsrModelId(null); + } + + // 构建模块配置 + buildModuleConfig( + agent.getAgentName(), + agent.getSystemPrompt(), + agent.getSummaryMemory(), + voice, + agent.getVadModelId(), + agent.getAsrModelId(), + agent.getLlmModelId(), + agent.getTtsModelId(), + agent.getMemModelId(), + agent.getIntentModelId(), + result, + true); + + return result; + } + + /** + * 构建配置信息 + * + * @param paramsList 系统参数列表 + * @return 配置信息 + */ + private Object buildConfig(Map config) { + + // 查询所有系统参数 + List paramsList = sysParamsService.list(new HashMap<>()); + + for (SysParamsDTO param : paramsList) { + String[] keys = param.getParamCode().split("\\."); + Map current = config; + + // 遍历除最后一个key之外的所有key + for (int i = 0; i < keys.length - 1; i++) { + String key = keys[i]; + if (!current.containsKey(key)) { + current.put(key, new HashMap()); + } + current = (Map) current.get(key); + } + + // 处理最后一个key + String lastKey = keys[keys.length - 1]; + String value = param.getParamValue(); + + // 根据valueType转换值 + switch (param.getValueType().toLowerCase()) { + case "number": + try { + double doubleValue = Double.parseDouble(value); + // 如果数值是整数形式,则转换为Integer + if (doubleValue == (int) doubleValue) { + current.put(lastKey, (int) doubleValue); + } else { + current.put(lastKey, doubleValue); + } + } catch (NumberFormatException e) { + current.put(lastKey, value); + } + break; + case "boolean": + current.put(lastKey, Boolean.parseBoolean(value)); + break; + case "array": + // 将分号分隔的字符串转换为数字数组 + List list = new ArrayList<>(); + for (String num : value.split(";")) { + if (StringUtils.isNotBlank(num)) { + list.add(num.trim()); + } + } + current.put(lastKey, list); + break; + case "json": + try { + current.put(lastKey, JsonUtils.parseObject(value, Object.class)); + } catch (Exception e) { + current.put(lastKey, value); + } + break; + default: + current.put(lastKey, value); + } + } + + return config; + } + + /** + * 构建模块配置 + * + * @param prompt 提示词 + * @param voice 音色 + * @param vadModelId VAD模型ID + * @param asrModelId ASR模型ID + * @param llmModelId LLM模型ID + * @param ttsModelId TTS模型ID + * @param memModelId 记忆模型ID + * @param intentModelId 意图模型ID + * @param result 结果Map + */ + private void buildModuleConfig( + String assistantName, + String prompt, + String summaryMemory, + String voice, + String vadModelId, + String asrModelId, + String llmModelId, + String ttsModelId, + String memModelId, + String intentModelId, + Map result, + boolean isCache) { + Map selectedModule = new HashMap<>(); + + String[] modelTypes = { "VAD", "ASR", "TTS", "Memory", "Intent", "LLM" }; + String[] modelIds = { vadModelId, asrModelId, ttsModelId, memModelId, intentModelId, llmModelId }; + String intentLLMModelId = null; + + for (int i = 0; i < modelIds.length; i++) { + if (modelIds[i] == null) { + continue; + } + ModelConfigEntity model = modelConfigService.getModelById(modelIds[i], isCache); + Map typeConfig = new HashMap<>(); + if (model.getConfigJson() != null) { + typeConfig.put(model.getId(), model.getConfigJson()); + // 如果是TTS类型,添加private_voice属性 + if ("TTS".equals(modelTypes[i]) && voice != null) { + ((Map) model.getConfigJson()).put("private_voice", voice); + } + // 如果是Intent类型,且type=intent_llm,则给他添加附加模型 + if ("Intent".equals(modelTypes[i])) { + Map map = (Map) model.getConfigJson(); + if ("intent_llm".equals(map.get("type"))) { + intentLLMModelId = (String) map.get("llm"); + if (intentLLMModelId != null && intentLLMModelId.equals(llmModelId)) { + intentLLMModelId = null; + } + } + if (map.get("functions") != null) { + String functionStr = (String) map.get("functions"); + if (StringUtils.isNotBlank(functionStr)) { + String[] functions = functionStr.split("\\;"); + map.put("functions", functions); + } + } + } + // 如果是LLM类型,且intentLLMModelId不为空,则添加附加模型 + if ("LLM".equals(modelTypes[i]) && intentLLMModelId != null) { + ModelConfigEntity intentLLM = modelConfigService.getModelById(intentLLMModelId, isCache); + typeConfig.put(intentLLM.getId(), intentLLM.getConfigJson()); + } + } + result.put(modelTypes[i], typeConfig); + + selectedModule.put(modelTypes[i], model.getId()); + } + + result.put("selected_module", selectedModule); + if (StringUtils.isNotBlank(prompt)) { + prompt = prompt.replace("{{assistant_name}}", StringUtils.isBlank(assistantName) ? "小智" : assistantName); + } + result.put("prompt", prompt); + result.put("summaryMemory", summaryMemory); + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java index 1c68a756..5b0bf4bc 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java @@ -7,6 +7,7 @@ import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -79,4 +80,16 @@ public class DeviceController { return new Result(); } + @PutMapping("/enableOta/{id}/{status}") + @Operation(summary = "启用/关闭OTA自动升级") + @RequiresPermissions("sys:role:normal") + public Result enableOtaUpgrade(@PathVariable String id, @PathVariable Integer status) { + DeviceEntity entity = deviceService.selectById(id); + if (entity == null) { + return new Result().error("设备不存在"); + } + entity.setAutoUpdate(status); + deviceService.updateById(entity); + return new Result(); + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAController.java index 4e3a2a95..05d36e9a 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAController.java @@ -15,48 +15,78 @@ import org.springframework.web.bind.annotation.RestController; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import xiaozhi.common.constant.Constant; import xiaozhi.modules.device.dto.DeviceReportReqDTO; import xiaozhi.modules.device.dto.DeviceReportRespDTO; +import xiaozhi.modules.device.entity.DeviceEntity; import xiaozhi.modules.device.service.DeviceService; -import xiaozhi.modules.device.utils.NetworkUtil; +import xiaozhi.modules.sys.service.SysParamsService; @Tag(name = "设备管理", description = "OTA 相关接口") +@Slf4j @RestController @RequiredArgsConstructor @RequestMapping("/ota/") public class OTAController { private final DeviceService deviceService; + private final SysParamsService sysParamsService; - @Operation(summary = "检查 OTA 版本和设备激活状态") + @Operation(summary = "OTA版本和设备激活状态检查") @PostMapping public ResponseEntity checkOTAVersion( @RequestBody DeviceReportReqDTO deviceReportReqDTO, - @Parameter(name = "Device-Id", description = "设备唯一标识", required = true, in = ParameterIn.HEADER) @RequestHeader("Device-Id") String deviceId, - - @Parameter(name = "Client-Id", description = "客户端标识", required = true, in = ParameterIn.HEADER) @RequestHeader("Client-Id") String clientId) { - if (StringUtils.isAnyBlank(deviceId, clientId)) { + @Parameter(name = "Client-Id", description = "客户端标识", required = false, in = ParameterIn.HEADER) @RequestHeader(value = "Client-Id", required = false) String clientId) { + if (StringUtils.isBlank(deviceId)) { return createResponse(DeviceReportRespDTO.createError("Device ID is required")); } + if (StringUtils.isBlank(clientId)) { + clientId = deviceId; + } String macAddress = deviceReportReqDTO.getMacAddress(); - boolean macAddressValid = NetworkUtil.isMacAddressValid(macAddress); + boolean macAddressValid = isMacAddressValid(macAddress); // 设备Id和Mac地址应是一致的, 并且必须需要application字段 if (!deviceId.equals(macAddress) || !macAddressValid || deviceReportReqDTO.getApplication() == null) { return createResponse(DeviceReportRespDTO.createError("Invalid OTA request")); } - return createResponse(deviceService.checkDeviceActive(macAddress, deviceId, clientId, deviceReportReqDTO)); + return createResponse(deviceService.checkDeviceActive(macAddress, clientId, deviceReportReqDTO)); + } + + @Operation(summary = "设备快速检查激活状态") + @PostMapping("activate") + public ResponseEntity activateDevice( + @Parameter(name = "Device-Id", description = "设备唯一标识", required = true, in = ParameterIn.HEADER) @RequestHeader("Device-Id") String deviceId, + @Parameter(name = "Client-Id", description = "客户端标识", required = false, in = ParameterIn.HEADER) @RequestHeader(value = "Client-Id", required = false) String clientId) { + if (StringUtils.isBlank(deviceId)) { + return ResponseEntity.status(202).build(); + } + DeviceEntity device = deviceService.getDeviceByMacAddress(deviceId); + if (device == null) { + return ResponseEntity.status(202).build(); + } + return ResponseEntity.ok("success"); } - @Operation(summary = "获取 OTA 提示信息") @GetMapping - public ResponseEntity getOTAPrompt() { - return createResponse(DeviceReportRespDTO.createError("请提交正确的ota参数")); + @Hidden + public ResponseEntity getOTA() { + String wsUrl = sysParamsService.getValue(Constant.SERVER_WEBSOCKET, true); + if (StringUtils.isBlank(wsUrl) || wsUrl.equals("null")) { + return ResponseEntity.ok("OTA接口不正常,缺少websocket地址,请登录智控台,在参数管理找到【server.websocket】配置"); + } + String otaUrl = sysParamsService.getValue(Constant.SERVER_OTA, true); + if (StringUtils.isBlank(otaUrl) || otaUrl.equals("null")) { + return ResponseEntity.ok("OTA接口不正常,缺少ota地址,请登录智控台,在参数管理找到【server.ota】配置"); + } + return ResponseEntity.ok("OTA接口运行正常,websocket集群数量:" + wsUrl.split(";").length); } @SneakyThrows @@ -71,4 +101,19 @@ public class OTAController { .contentLength(jsonBytes.length) .body(json); } + + /** + * 简单判断mac地址是否有效(非严格) + * + * @param macAddress + * @return + */ + private boolean isMacAddressValid(String macAddress) { + if (StringUtils.isBlank(macAddress)) { + return false; + } + // MAC地址通常为12位十六进制数字,可以包含冒号或连字符分隔符 + String macPattern = "^([0-9A-Za-z]{2}[:-]){5}([0-9A-Za-z]{2})$"; + return macAddress.matches(macPattern); + } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAMagController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAMagController.java new file mode 100644 index 00000000..bbe0c66a --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OTAMagController.java @@ -0,0 +1,291 @@ +package xiaozhi.modules.device.controller; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Map; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import xiaozhi.common.constant.Constant; +import xiaozhi.common.page.PageData; +import xiaozhi.common.redis.RedisKeys; +import xiaozhi.common.redis.RedisUtils; +import xiaozhi.common.utils.Result; +import xiaozhi.common.validator.ValidatorUtils; +import xiaozhi.modules.device.entity.OtaEntity; +import xiaozhi.modules.device.service.OtaService; + +@Tag(name = "设备管理", description = "OTA 相关接口") +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/otaMag") +public class OTAMagController { + private static final Logger logger = LoggerFactory.getLogger(OTAController.class); + private final OtaService otaService; + private final RedisUtils redisUtils; + + @GetMapping + @Operation(summary = "分页查询 OTA 固件信息") + @Parameters({ + @Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true), + @Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true) + }) + @RequiresPermissions("sys:role:superAdmin") + public Result> page(@Parameter(hidden = true) @RequestParam Map params) { + ValidatorUtils.validateEntity(params); + PageData page = otaService.page(params); + return new Result>().ok(page); + } + + @GetMapping("{id}") + @Operation(summary = "信息 OTA 固件信息") + @RequiresPermissions("sys:role:superAdmin") + public Result get(@PathVariable("id") String id) { + OtaEntity data = otaService.selectById(id); + return new Result().ok(data); + } + + @PostMapping + @Operation(summary = "保存 OTA 固件信息") + @RequiresPermissions("sys:role:superAdmin") + public Result save(@RequestBody OtaEntity entity) { + if (entity == null) { + return new Result().error("固件信息不能为空"); + } + if (StringUtils.isBlank(entity.getFirmwareName())) { + return new Result().error("固件名称不能为空"); + } + if (StringUtils.isBlank(entity.getType())) { + return new Result().error("固件类型不能为空"); + } + if (StringUtils.isBlank(entity.getVersion())) { + return new Result().error("版本号不能为空"); + } + try { + otaService.save(entity); + return new Result(); + } catch (RuntimeException e) { + return new Result().error(e.getMessage()); + } + } + + @DeleteMapping("/{id}") + @Operation(summary = "OTA 删除") + @RequiresPermissions("sys:role:superAdmin") + public Result delete(@PathVariable("id") String[] ids) { + if (ids == null || ids.length == 0) { + return new Result().error("删除的固件ID不能为空"); + } + otaService.delete(ids); + return new Result(); + } + + @PutMapping("/{id}") + @Operation(summary = "修改 OTA 固件信息") + @RequiresPermissions("sys:role:superAdmin") + public Result update(@PathVariable("id") String id, @RequestBody OtaEntity entity) { + if (entity == null) { + return new Result<>().error("固件信息不能为空"); + } + entity.setId(id); + try { + otaService.update(entity); + return new Result<>(); + } catch (RuntimeException e) { + return new Result<>().error(e.getMessage()); + } + } + + @GetMapping("/getDownloadUrl/{id}") + @Operation(summary = "获取 OTA 固件下载链接") + @RequiresPermissions("sys:role:superAdmin") + public Result getDownloadUrl(@PathVariable("id") String id) { + String uuid = UUID.randomUUID().toString(); + redisUtils.set(RedisKeys.getOtaIdKey(uuid), id); + return new Result().ok(uuid); + } + + @GetMapping("/download/{uuid}") + @Operation(summary = "下载固件文件") + public ResponseEntity downloadFirmware(@PathVariable("uuid") String uuid) { + String id = (String) redisUtils.get(RedisKeys.getOtaIdKey(uuid)); + if (StringUtils.isBlank(id)) { + return ResponseEntity.notFound().build(); + } + + // 检查下载次数 + String downloadCountKey = RedisKeys.getOtaDownloadCountKey(uuid); + Integer downloadCount = (Integer) redisUtils.get(downloadCountKey); + if (downloadCount == null) { + downloadCount = 0; + } + + // 如果下载次数超过3次,返回404 + if (downloadCount >= 3) { + redisUtils.delete(downloadCountKey); + redisUtils.delete(RedisKeys.getOtaIdKey(uuid)); + logger.warn("Download limit exceeded for UUID: {}", uuid); + return ResponseEntity.notFound().build(); + } + + redisUtils.set(downloadCountKey, downloadCount + 1); + + try { + // 获取固件信息 + OtaEntity otaEntity = otaService.selectById(id); + if (otaEntity == null || StringUtils.isBlank(otaEntity.getFirmwarePath())) { + logger.warn("Firmware not found or path is empty for ID: {}", id); + return ResponseEntity.notFound().build(); + } + + // 获取文件路径 - 确保路径是绝对路径或正确的相对路径 + String firmwarePath = otaEntity.getFirmwarePath(); + Path path; + + // 检查是否是绝对路径 + if (Paths.get(firmwarePath).isAbsolute()) { + path = Paths.get(firmwarePath); + } else { + // 如果是相对路径,则从当前工作目录解析 + path = Paths.get(System.getProperty("user.dir"), firmwarePath); + } + + logger.info("Attempting to download firmware for ID: {}, DB path: {}, resolved path: {}", + id, firmwarePath, path.toAbsolutePath()); + + if (!Files.exists(path) || !Files.isRegularFile(path)) { + // 尝试直接从firmware目录下查找文件名 + String fileName = new File(firmwarePath).getName(); + Path altPath = Paths.get(System.getProperty("user.dir"), "firmware", fileName); + + logger.info("File not found at primary path, trying alternative path: {}", altPath.toAbsolutePath()); + + if (Files.exists(altPath) && Files.isRegularFile(altPath)) { + path = altPath; + } else { + logger.error("Firmware file not found at either path: {} or {}", + path.toAbsolutePath(), altPath.toAbsolutePath()); + return ResponseEntity.notFound().build(); + } + } + + // 读取文件内容 + byte[] fileContent = Files.readAllBytes(path); + + // 设置响应头 + String originalFilename = otaEntity.getType() + "_" + otaEntity.getVersion(); + if (firmwarePath.contains(".")) { + String extension = firmwarePath.substring(firmwarePath.lastIndexOf(".")); + originalFilename += extension; + } + + // 清理文件名,移除不安全字符 + String safeFilename = originalFilename.replaceAll("[^a-zA-Z0-9._-]", "_"); + + logger.info("Providing download for firmware ID: {}, filename: {}, size: {} bytes", + id, safeFilename, fileContent.length); + + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + safeFilename + "\"") + .body(fileContent); + } catch (IOException e) { + logger.error("Error reading firmware file for ID: {}", id, e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } catch (Exception e) { + logger.error("Unexpected error during firmware download for ID: {}", id, e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + @PostMapping("/upload") + @Operation(summary = "上传固件文件") + @RequiresPermissions("sys:role:superAdmin") + public Result uploadFirmware(@RequestParam("file") MultipartFile file) { + if (file.isEmpty()) { + return new Result().error("上传文件不能为空"); + } + + // 检查文件扩展名 + String originalFilename = file.getOriginalFilename(); + if (originalFilename == null) { + return new Result().error("文件名不能为空"); + } + + String extension = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase(); + if (!extension.equals(".bin") && !extension.equals(".apk")) { + return new Result().error("只允许上传.bin和.apk格式的文件"); + } + + try { + // 计算文件的MD5值 + String md5 = calculateMD5(file); + + // 设置存储路径 + String uploadDir = "uploadfile"; + Path uploadPath = Paths.get(uploadDir); + + // 如果目录不存在,创建目录 + if (!Files.exists(uploadPath)) { + Files.createDirectories(uploadPath); + } + + // 使用MD5作为文件名,固定使用.bin扩展名 + String uniqueFileName = md5 + extension; + Path filePath = uploadPath.resolve(uniqueFileName); + + // 检查文件是否已存在 + if (Files.exists(filePath)) { + return new Result().ok(filePath.toString()); + } + + // 保存文件 + Files.copy(file.getInputStream(), filePath); + + // 返回文件路径 + return new Result().ok(filePath.toString()); + } catch (IOException | NoSuchAlgorithmException e) { + return new Result().error("文件上传失败:" + e.getMessage()); + } + } + + private String calculateMD5(MultipartFile file) throws IOException, NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] digest = md.digest(file.getBytes()); + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java index 5ede3d6e..f3fdb7a3 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java @@ -1,5 +1,7 @@ package xiaozhi.modules.device.dao; +import java.util.Date; + import org.apache.ibatis.annotations.Mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; @@ -8,4 +10,12 @@ import xiaozhi.modules.device.entity.DeviceEntity; @Mapper public interface DeviceDao extends BaseMapper { + /** + * 获取此智能体全部设备的最后连接时间 + * + * @param agentId 智能体id + * @return + */ + Date getAllLastConnectedAtByAgentId(String agentId); + } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dao/OtaDao.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dao/OtaDao.java new file mode 100644 index 00000000..975f69be --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/dao/OtaDao.java @@ -0,0 +1,15 @@ +package xiaozhi.modules.device.dao; + +import org.apache.ibatis.annotations.Mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import xiaozhi.modules.device.entity.OtaEntity; + +/** + * OTA固件管理 + */ +@Mapper +public interface OtaDao extends BaseMapper { + +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceReportRespDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceReportRespDTO.java index 9f93a9b7..f938001e 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceReportRespDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceReportRespDTO.java @@ -19,6 +19,9 @@ public class DeviceReportRespDTO { @Schema(description = "固件版本信息") private Firmware firmware; + + @Schema(description = "WebSocket配置") + private Websocket websocket; @Getter @Setter @@ -44,6 +47,8 @@ public class DeviceReportRespDTO { @Schema(description = "激活码信息: 激活地址") private String message; + @Schema(description = "挑战码") + private String challenge; } @Getter @@ -58,4 +63,11 @@ public class DeviceReportRespDTO { @Schema(description = "时区偏移量,单位为分钟") private Integer timezone_offset; } + + @Getter + @Setter + public static class Websocket { + @Schema(description = "WebSocket服务器地址") + private String url; + } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/entity/OtaEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/device/entity/OtaEntity.java new file mode 100644 index 00000000..b93c608b --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/entity/OtaEntity.java @@ -0,0 +1,61 @@ +package xiaozhi.modules.device.entity; + +import java.util.Date; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("ai_ota") +@Schema(description = "固件信息") +public class OtaEntity { + + @TableId(type = IdType.ASSIGN_UUID) + @Schema(description = "ID") + private String id; + + @Schema(description = "固件名称") + private String firmwareName; + + @Schema(description = "固件类型") + private String type; + + @Schema(description = "版本号") + private String version; + + @Schema(description = "文件大小(字节)") + private Long size; + + @Schema(description = "备注/说明") + private String remark; + + @Schema(description = "固件路径") + private String firmwarePath; + + @Schema(description = "排序") + private Integer sort; + + @Schema(description = "更新者") + @TableField(fill = FieldFill.UPDATE) + private Long updater; + + @Schema(description = "更新时间") + @TableField(fill = FieldFill.UPDATE) + private Date updateDate; + + @Schema(description = "创建者") + @TableField(fill = FieldFill.INSERT) + private Long creator; + + @Schema(description = "创建时间") + @TableField(fill = FieldFill.INSERT) + private Date createDate; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java index 37ff8d49..fb91ff89 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java @@ -1,25 +1,22 @@ package xiaozhi.modules.device.service; +import java.util.Date; import java.util.List; import xiaozhi.common.page.PageData; +import xiaozhi.common.service.BaseService; import xiaozhi.modules.device.dto.DevicePageUserDTO; import xiaozhi.modules.device.dto.DeviceReportReqDTO; import xiaozhi.modules.device.dto.DeviceReportRespDTO; import xiaozhi.modules.device.entity.DeviceEntity; import xiaozhi.modules.device.vo.UserShowDeviceListVO; -public interface DeviceService { - - /** - * 根据Mac地址获取设备信息 - */ - DeviceEntity getDeviceById(String macAddress); +public interface DeviceService extends BaseService { /** * 检查设备是否激活 */ - DeviceReportRespDTO checkDeviceActive(String macAddress, String deviceId, String clientId, + DeviceReportRespDTO checkDeviceActive(String macAddress, String clientId, DeviceReportReqDTO deviceReport); /** @@ -66,4 +63,29 @@ public interface DeviceService { * @return 用户列表分页数据 */ PageData page(DevicePageUserDTO dto); + + /** + * 根据MAC地址获取设备信息 + * + * @param macAddress MAC地址 + * @return 设备信息 + */ + DeviceEntity getDeviceByMacAddress(String macAddress); + + /** + * 根据设备ID获取激活码 + * + * @param deviceId 设备ID + * @return 激活码 + */ + String geCodeByDeviceId(String deviceId); + + /** + * 获取这个智能体设备理的最近的最后连接时间 + * @param agentId 智能体id + * @return 返回设备最近的最后连接时间 + */ + Date getLatestLastConnectionTime(String agentId); + + } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/OtaService.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/OtaService.java new file mode 100644 index 00000000..cd35f6ae --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/OtaService.java @@ -0,0 +1,22 @@ +package xiaozhi.modules.device.service; + +import java.util.Map; + +import xiaozhi.common.page.PageData; +import xiaozhi.common.service.BaseService; +import xiaozhi.modules.device.entity.OtaEntity; + +/** + * OTA固件管理 + */ +public interface OtaService extends BaseService { + PageData page(Map params); + + boolean save(OtaEntity entity); + + void update(OtaEntity entity); + + void delete(String[] ids); + + OtaEntity getLatestOta(String type); +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java index a9797f42..ee31c41f 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java @@ -6,22 +6,28 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; -import java.util.concurrent.TimeUnit; +import java.util.UUID; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.aop.framework.AopContext; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import cn.hutool.core.util.RandomUtil; +import jakarta.servlet.http.HttpServletRequest; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import xiaozhi.common.constant.Constant; import xiaozhi.common.exception.RenException; import xiaozhi.common.page.PageData; +import xiaozhi.common.redis.RedisKeys; +import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.user.UserDetail; import xiaozhi.common.utils.ConvertUtils; @@ -31,37 +37,41 @@ import xiaozhi.modules.device.dto.DevicePageUserDTO; import xiaozhi.modules.device.dto.DeviceReportReqDTO; import xiaozhi.modules.device.dto.DeviceReportRespDTO; import xiaozhi.modules.device.entity.DeviceEntity; +import xiaozhi.modules.device.entity.OtaEntity; import xiaozhi.modules.device.service.DeviceService; +import xiaozhi.modules.device.service.OtaService; import xiaozhi.modules.device.vo.UserShowDeviceListVO; import xiaozhi.modules.security.user.SecurityUser; +import xiaozhi.modules.sys.service.SysParamsService; import xiaozhi.modules.sys.service.SysUserUtilService; +@Slf4j @Service +@AllArgsConstructor public class DeviceServiceImpl extends BaseServiceImpl implements DeviceService { private final DeviceDao deviceDao; - private final SysUserUtilService sysUserUtilService; + private final SysParamsService sysParamsService; + private final RedisUtils redisUtils; + private final OtaService otaService; - private final String frontedUrl; - - private final RedisTemplate redisTemplate; - - // 添加构造函数来初始化 deviceMapper - public DeviceServiceImpl(DeviceDao deviceDao, SysUserUtilService sysUserUtilService, - @Value("${app.fronted-url:http://localhost:8001}") String frontedUrl, - RedisTemplate redisTemplate) { - this.deviceDao = deviceDao; - this.sysUserUtilService = sysUserUtilService; - this.frontedUrl = frontedUrl; - this.redisTemplate = redisTemplate; - } - - @Override - public DeviceEntity getDeviceById(String deviceId) { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(DeviceEntity::getId, deviceId); - return deviceDao.selectOne(queryWrapper); + @Async + public void updateDeviceConnectionInfo(String agentId, String deviceId, String appVersion) { + try { + DeviceEntity device = new DeviceEntity(); + device.setId(deviceId); + device.setLastConnectedAt(new Date()); + if (StringUtils.isNotBlank(appVersion)) { + device.setAppVersion(appVersion); + } + deviceDao.updateById(device); + if (StringUtils.isNotBlank(agentId)) { + redisUtils.set(RedisKeys.getAgentDeviceLastConnectedAtById(agentId), new Date()); + } + } catch (Exception e) { + log.error("异步更新设备连接信息失败", e); + } } @Override @@ -70,14 +80,14 @@ public class DeviceServiceImpl extends BaseServiceImpl throw new RenException("激活码不能为空"); } String deviceKey = "ota:activation:code:" + activationCode; - Object cacheDeviceId = redisTemplate.opsForValue().get(deviceKey); + Object cacheDeviceId = redisUtils.get(deviceKey); if (cacheDeviceId == null) { throw new RenException("激活码错误"); } String deviceId = (String) cacheDeviceId; String safeDeviceId = deviceId.replace(":", "_").toLowerCase(); String cacheDeviceKey = String.format("ota:activation:data:%s", safeDeviceId); - Map cacheMap = redisTemplate.opsForHash().entries(cacheDeviceKey); + Map cacheMap = (Map) redisUtils.get(cacheDeviceKey); if (cacheMap == null) { throw new RenException("激活码错误"); } @@ -107,6 +117,7 @@ public class DeviceServiceImpl extends BaseServiceImpl deviceEntity.setMacAddress(macAddress); deviceEntity.setUserId(user.getId()); deviceEntity.setCreator(user.getId()); + deviceEntity.setAutoUpdate(1); deviceEntity.setCreateDate(currentTime); deviceEntity.setUpdater(user.getId()); deviceEntity.setUpdateDate(currentTime); @@ -114,64 +125,66 @@ public class DeviceServiceImpl extends BaseServiceImpl deviceDao.insert(deviceEntity); // 清理redis缓存 - redisTemplate.delete(cacheDeviceKey); - redisTemplate.delete(deviceKey); + redisUtils.delete(cacheDeviceKey); + redisUtils.delete(deviceKey); return true; } @Override - public DeviceReportRespDTO checkDeviceActive(String macAddress, String deviceId, String clientId, + public DeviceReportRespDTO checkDeviceActive(String macAddress, String clientId, DeviceReportReqDTO deviceReport) { DeviceReportRespDTO response = new DeviceReportRespDTO(); response.setServer_time(buildServerTime()); - // todo: 此处是固件信息,目前是针对固件上传上来的版本号再返回回去 - // 在未来开发了固件更新功能,需要更换此处代码, - // 或写定时任务定期请求虾哥的OTA,获取最新的版本讯息保存到服务内 - DeviceReportRespDTO.Firmware firmware = new DeviceReportRespDTO.Firmware(); - firmware.setVersion(deviceReport.getApplication().getVersion()); - firmware.setUrl("http://localhost:8002/xiaozhi/ota/download"); - response.setFirmware(firmware); - DeviceEntity deviceById = getDeviceById(deviceId); - if (deviceById != null) { // 如果设备存在,则更新上次连接时间 - deviceById.setLastConnectedAt(new Date()); - deviceDao.updateById(deviceById); - } else { // 如果设备不存在,则生成激活码 - String safeDeviceId = deviceId.replace(":", "_").toLowerCase(); - String dataKey = String.format("ota:activation:data:%s", safeDeviceId); + DeviceEntity deviceById = getDeviceByMacAddress(macAddress); - Map cacheMap = redisTemplate.opsForHash().entries(dataKey); - DeviceReportRespDTO.Activation code = new DeviceReportRespDTO.Activation(); - - if (cacheMap != null && cacheMap.containsKey("activation_code")) { - String cachedCode = (String) cacheMap.get("activation_code"); - code.setCode(cachedCode); - code.setMessage(frontedUrl + "\n" + cachedCode); - } else { - String newCode = RandomUtil.randomNumbers(6); - code.setCode(newCode); - code.setMessage(frontedUrl + "\n" + newCode); - - Map dataMap = new HashMap<>(); - dataMap.put("id", deviceId); - dataMap.put("mac_address", macAddress); - dataMap.put("board", (deviceReport.getChipModelName() != null) ? deviceReport.getChipModelName() - : (deviceReport.getBoard() != null ? deviceReport.getBoard().getType() : "unknown")); - dataMap.put("app_version", (deviceReport.getApplication() != null) - ? deviceReport.getApplication().getVersion() - : null); - dataMap.put("deviceId", deviceId); - dataMap.put("activation_code", newCode); - - // 写入主数据 key - redisTemplate.opsForHash().putAll(dataKey, dataMap); - redisTemplate.expire(dataKey, 24, TimeUnit.HOURS); - - // 写入反查激活码 key - String codeKey = "ota:activation:code:" + newCode; - redisTemplate.opsForValue().set(codeKey, deviceId, 24, TimeUnit.HOURS); + // 设备未绑定,则返回当前上传的固件信息(不更新)以此兼容旧固件版本 + if (deviceById == null) { + DeviceReportRespDTO.Firmware firmware = new DeviceReportRespDTO.Firmware(); + firmware.setVersion(deviceReport.getApplication().getVersion()); + firmware.setUrl(Constant.INVALID_FIRMWARE_URL); + response.setFirmware(firmware); + } else { + // 只有在设备已绑定且autoUpdate不为0的情况下才返回固件升级信息 + if (deviceById.getAutoUpdate() != 0) { + String type = deviceReport.getBoard() == null ? null : deviceReport.getBoard().getType(); + DeviceReportRespDTO.Firmware firmware = buildFirmwareInfo(type, + deviceReport.getApplication() == null ? null : deviceReport.getApplication().getVersion()); + response.setFirmware(firmware); } + } + // 添加WebSocket配置 + DeviceReportRespDTO.Websocket websocket = new DeviceReportRespDTO.Websocket(); + // 从系统参数获取WebSocket URL,如果未配置则使用默认值 + String wsUrl = sysParamsService.getValue(Constant.SERVER_WEBSOCKET, true); + if (StringUtils.isBlank(wsUrl) || wsUrl.equals("null")) { + log.error("WebSocket地址未配置,请登录智控台,在参数管理找到【server.websocket】配置"); + wsUrl = "ws://xiaozhi.server.com:8000/xiaozhi/v1/"; + websocket.setUrl(wsUrl); + } else { + String[] wsUrls = wsUrl.split("\\;"); + if (wsUrls.length > 0) { + // 随机选择一个WebSocket URL + websocket.setUrl(wsUrls[RandomUtil.randomInt(0, wsUrls.length)]); + } else { + log.error("WebSocket地址未配置,请登录智控台,在参数管理找到【server.websocket】配置"); + websocket.setUrl("ws://xiaozhi.server.com:8000/xiaozhi/v1/"); + } + } + + response.setWebsocket(websocket); + + if (deviceById != null) { + // 如果设备存在,则异步更新上次连接时间和版本信息 + String appVersion = deviceReport.getApplication() != null ? deviceReport.getApplication().getVersion() + : null; + // 通过Spring代理调用异步方法 + ((DeviceServiceImpl) AopContext.currentProxy()).updateDeviceConnectionInfo(deviceById.getAgentId(), + deviceById.getId(), appVersion); + } else { + // 如果设备不存在,则生成激活码 + DeviceReportRespDTO.Activation code = buildActivation(macAddress, deviceReport); response.setActivation(code); } @@ -221,7 +234,7 @@ public class DeviceServiceImpl extends BaseServiceImpl params.put(Constant.PAGE, dto.getPage()); params.put(Constant.LIMIT, dto.getLimit()); IPage page = baseDao.selectPage( - getPage(params, "sort", true), + getPage(params, "mac_address", true), // 定义查询条件 new QueryWrapper() // 必须设备关键词查找 @@ -240,6 +253,16 @@ public class DeviceServiceImpl extends BaseServiceImpl return new PageData<>(list, page.getTotal()); } + @Override + public DeviceEntity getDeviceByMacAddress(String macAddress) { + if (StringUtils.isBlank(macAddress)) { + return null; + } + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("mac_address", macAddress); + return baseDao.selectOne(wrapper); + } + private DeviceReportRespDTO.ServerTime buildServerTime() { DeviceReportRespDTO.ServerTime serverTime = new DeviceReportRespDTO.ServerTime(); TimeZone tz = TimeZone.getDefault(); @@ -248,4 +271,143 @@ public class DeviceServiceImpl extends BaseServiceImpl serverTime.setTimezone_offset(tz.getOffset(System.currentTimeMillis()) / (60 * 1000)); return serverTime; } -} \ No newline at end of file + + @Override + public String geCodeByDeviceId(String deviceId) { + String dataKey = getDeviceCacheKey(deviceId); + + Map cacheMap = (Map) redisUtils.get(dataKey); + if (cacheMap != null && cacheMap.containsKey("activation_code")) { + String cachedCode = (String) cacheMap.get("activation_code"); + return cachedCode; + } + return null; + } + + @Override + public Date getLatestLastConnectionTime(String agentId) { + // 查询是否有缓存时间,有则返回 + Date cachedDate = (Date) redisUtils.get(RedisKeys.getAgentDeviceLastConnectedAtById(agentId)); + if (cachedDate != null) { + return cachedDate; + } + Date maxDate = deviceDao.getAllLastConnectedAtByAgentId(agentId); + if (maxDate != null) { + redisUtils.set(RedisKeys.getAgentDeviceLastConnectedAtById(agentId), maxDate); + } + return maxDate; + } + + private String getDeviceCacheKey(String deviceId) { + String safeDeviceId = deviceId.replace(":", "_").toLowerCase(); + String dataKey = String.format("ota:activation:data:%s", safeDeviceId); + return dataKey; + } + + public DeviceReportRespDTO.Activation buildActivation(String deviceId, DeviceReportReqDTO deviceReport) { + DeviceReportRespDTO.Activation code = new DeviceReportRespDTO.Activation(); + + String cachedCode = geCodeByDeviceId(deviceId); + + if (StringUtils.isNotBlank(cachedCode)) { + code.setCode(cachedCode); + String frontedUrl = sysParamsService.getValue(Constant.SERVER_FRONTED_URL, true); + code.setMessage(frontedUrl + "\n" + cachedCode); + code.setChallenge(deviceId); + } else { + String newCode = RandomUtil.randomNumbers(6); + code.setCode(newCode); + String frontedUrl = sysParamsService.getValue(Constant.SERVER_FRONTED_URL, true); + code.setMessage(frontedUrl + "\n" + newCode); + code.setChallenge(deviceId); + + Map dataMap = new HashMap<>(); + dataMap.put("id", deviceId); + dataMap.put("mac_address", deviceId); + + dataMap.put("board", (deviceReport.getBoard() != null && deviceReport.getBoard().getType() != null) + ? deviceReport.getBoard().getType() + : (deviceReport.getChipModelName() != null ? deviceReport.getChipModelName() : "unknown")); + dataMap.put("app_version", (deviceReport.getApplication() != null) + ? deviceReport.getApplication().getVersion() + : null); + + dataMap.put("deviceId", deviceId); + dataMap.put("activation_code", newCode); + + // 写入主数据 key + String dataKey = getDeviceCacheKey(deviceId); + redisUtils.set(dataKey, dataMap); + + // 写入反查激活码 key + String codeKey = "ota:activation:code:" + newCode; + redisUtils.set(codeKey, deviceId); + } + return code; + } + + private DeviceReportRespDTO.Firmware buildFirmwareInfo(String type, String currentVersion) { + if (StringUtils.isBlank(type)) { + return null; + } + if (StringUtils.isBlank(currentVersion)) { + currentVersion = "0.0.0"; + } + + OtaEntity ota = otaService.getLatestOta(type); + DeviceReportRespDTO.Firmware firmware = new DeviceReportRespDTO.Firmware(); + String downloadUrl = null; + + if (ota != null) { + // 如果设备没有版本信息,或者OTA版本比设备版本新,则返回下载地址 + if (compareVersions(ota.getVersion(), currentVersion) > 0) { + String otaUrl = sysParamsService.getValue(Constant.SERVER_OTA, true); + if (StringUtils.isBlank(otaUrl) || otaUrl.equals("null")) { + log.error("OTA地址未配置,请登录智控台,在参数管理找到【server.ota】配置"); + // 尝试从请求中获取 + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder + .getRequestAttributes()) + .getRequest(); + otaUrl = request.getRequestURL().toString(); + } + // 将URL中的/ota/替换为/otaMag/download/ + String uuid = UUID.randomUUID().toString(); + redisUtils.set(RedisKeys.getOtaIdKey(uuid), ota.getId()); + downloadUrl = otaUrl.replace("/ota/", "/otaMag/download/") + uuid; + } + } + + firmware.setVersion(ota == null ? currentVersion : ota.getVersion()); + firmware.setUrl(downloadUrl == null ? Constant.INVALID_FIRMWARE_URL : downloadUrl); + return firmware; + } + + /** + * 比较两个版本号 + * + * @param version1 版本1 + * @param version2 版本2 + * @return 如果version1 > version2返回1,version1 < version2返回-1,相等返回0 + */ + private static int compareVersions(String version1, String version2) { + if (version1 == null || version2 == null) { + return 0; + } + + String[] v1Parts = version1.split("\\."); + String[] v2Parts = version2.split("\\."); + + int length = Math.max(v1Parts.length, v2Parts.length); + for (int i = 0; i < length; i++) { + int v1 = i < v1Parts.length ? Integer.parseInt(v1Parts[i]) : 0; + int v2 = i < v2Parts.length ? Integer.parseInt(v2Parts[i]) : 0; + + if (v1 > v2) { + return 1; + } else if (v1 < v2) { + return -1; + } + } + return 0; + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/OtaServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/OtaServiceImpl.java new file mode 100644 index 00000000..ed9e8c7f --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/OtaServiceImpl.java @@ -0,0 +1,85 @@ +package xiaozhi.modules.device.service.impl; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; + +import io.micrometer.common.util.StringUtils; +import xiaozhi.common.page.PageData; +import xiaozhi.common.service.impl.BaseServiceImpl; +import xiaozhi.modules.device.dao.OtaDao; +import xiaozhi.modules.device.entity.OtaEntity; +import xiaozhi.modules.device.service.OtaService; + +@Service +public class OtaServiceImpl extends BaseServiceImpl implements OtaService { + + @Override + public PageData page(Map params) { + IPage page = baseDao.selectPage( + getPage(params, "update_date", true), + getWrapper(params)); + + return new PageData<>(page.getRecords(), page.getTotal()); + } + + private QueryWrapper getWrapper(Map params) { + String firmwareName = (String) params.get("firmwareName"); + + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.like(StringUtils.isNotBlank(firmwareName), "firmware_name", firmwareName); + + return wrapper; + } + + @Override + public void update(OtaEntity entity) { + // 检查是否存在相同类型和版本的固件(排除当前记录) + QueryWrapper queryWrapper = new QueryWrapper() + .eq("type", entity.getType()) + .eq("version", entity.getVersion()) + .ne("id", entity.getId()); // 排除当前记录 + + if (baseDao.selectCount(queryWrapper) > 0) { + throw new RuntimeException("已存在相同类型和版本的固件,请修改后重试"); + } + + entity.setUpdateDate(new Date()); + baseDao.updateById(entity); + } + + @Override + public void delete(String[] ids) { + baseDao.deleteBatchIds(Arrays.asList(ids)); + } + + @Override + public boolean save(OtaEntity entity) { + QueryWrapper queryWrapper = new QueryWrapper() + .eq("type", entity.getType()); + // 同类固件只保留最新的一条 + List otaList = baseDao.selectList(queryWrapper); + if (otaList != null && otaList.size() > 0) { + OtaEntity otaBefore = otaList.getFirst(); + entity.setId(otaBefore.getId()); + baseDao.updateById(entity); + return true; + } + return baseDao.insert(entity) > 0; + } + + @Override + public OtaEntity getLatestOta(String type) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("type", type) + .orderByDesc("update_date") + .last("LIMIT 1"); + return baseDao.selectOne(wrapper); + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/utils/NetworkUtil.java b/main/manager-api/src/main/java/xiaozhi/modules/device/utils/NetworkUtil.java deleted file mode 100644 index ab269f7b..00000000 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/utils/NetworkUtil.java +++ /dev/null @@ -1,34 +0,0 @@ -package xiaozhi.modules.device.utils; - -import java.util.regex.Pattern; - -import org.apache.commons.lang3.StringUtils; - -/** - * 网络工具类 - */ -public class NetworkUtil { - /** - * MAC地址正则表达式 - */ - private static final Pattern macPattern = Pattern.compile("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"); - - /** - * 判断MAC地址是否合法 - */ - public static boolean isMacAddressValid(String mac) { - if (StringUtils.isBlank(mac)) { - return false; - } - // 正则校验格式 - if (!macPattern.matcher(mac).matches()) { - return false; - } - // 校验MAC地址是否为单播地址 - String normalized = mac.toLowerCase(); - String[] parts = normalized.split("[:-]"); - int firstByte = Integer.parseInt(parts[0], 16); - return (firstByte & 1) == 0; // 最低位为0表示单播地址,合法 - } - -} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelController.java b/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelController.java index 0ce42157..8baff29d 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelController.java @@ -19,6 +19,8 @@ import lombok.AllArgsConstructor; import xiaozhi.common.page.PageData; import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.Result; +import xiaozhi.modules.agent.service.AgentTemplateService; +import xiaozhi.modules.config.service.ConfigService; import xiaozhi.modules.model.dto.ModelBasicInfoDTO; import xiaozhi.modules.model.dto.ModelConfigBodyDTO; import xiaozhi.modules.model.dto.ModelConfigDTO; @@ -38,6 +40,8 @@ public class ModelController { private final ModelProviderService modelProviderService; private final TimbreService timbreService; private final ModelConfigService modelConfigService; + private final ConfigService configService; + private final AgentTemplateService agentTemplateService; @GetMapping("/names") @Operation(summary = "获取所有模型名称") @@ -75,6 +79,7 @@ public class ModelController { @PathVariable String provideCode, @RequestBody ModelConfigBodyDTO modelConfigBodyDTO) { ModelConfigDTO modelConfigDTO = modelConfigService.add(modelType, provideCode, modelConfigBodyDTO); + configService.getConfig(false); return new Result().ok(modelConfigDTO); } @@ -86,6 +91,7 @@ public class ModelController { @PathVariable String id, @RequestBody ModelConfigBodyDTO modelConfigBodyDTO) { ModelConfigDTO modelConfigDTO = modelConfigService.edit(modelType, provideCode, id, modelConfigBodyDTO); + configService.getConfig(false); return new Result().ok(modelConfigDTO); } @@ -119,6 +125,27 @@ public class ModelController { return new Result(); } + @PutMapping("/default/{id}") + @Operation(summary = "设置默认模型") + @RequiresPermissions("sys:role:superAdmin") + public Result setDefaultModel(@PathVariable String id) { + ModelConfigEntity entity = modelConfigService.selectById(id); + if (entity == null) { + return new Result().error("模型配置不存在"); + } + // 将其他模型设置为非默认 + modelConfigService.setDefaultModel(entity.getModelType(), 0); + entity.setIsEnabled(1); + entity.setIsDefault(1); + modelConfigService.updateById(entity); + + // 更新模板表中对应的模型ID + agentTemplateService.updateDefaultTemplateModelId(entity.getModelType(), entity.getId()); + + configService.getConfig(false); + return new Result(); + } + @GetMapping("/{modelId}/voices") @Operation(summary = "获取模型音色") @RequiresPermissions("sys:role:normal") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelProviderController.java b/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelProviderController.java new file mode 100644 index 00000000..fc534a0a --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelProviderController.java @@ -0,0 +1,68 @@ +package xiaozhi.modules.model.controller; + +import java.util.List; + +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import xiaozhi.common.page.PageData; +import xiaozhi.common.utils.Result; +import xiaozhi.common.validator.group.UpdateGroup; +import xiaozhi.modules.model.dto.ModelProviderDTO; +import xiaozhi.modules.model.service.ModelProviderService; + +@AllArgsConstructor +@RestController +@RequestMapping("/models/provider") +@Tag(name = "模型供应器") +public class ModelProviderController { + + private final ModelProviderService modelProviderService; + + @GetMapping + @Operation(summary = "获取模型供应器列表") + @RequiresPermissions("sys:role:superAdmin") + public Result> getListPage(ModelProviderDTO modelProviderDTO, + @RequestParam(required = true, defaultValue = "0") String page, + @RequestParam(required = true, defaultValue = "10") String limit) { + return new Result>() + .ok(modelProviderService.getListPage(modelProviderDTO, page, limit)); + } + + @PostMapping + @Operation(summary = "新增模型供应器") + @RequiresPermissions("sys:role:superAdmin") + public Result add(@RequestBody @Validated ModelProviderDTO modelProviderDTO) { + ModelProviderDTO resp = modelProviderService.add(modelProviderDTO); + return new Result().ok(resp); + } + + @PutMapping + @Operation(summary = "修改模型供应器") + @RequiresPermissions("sys:role:superAdmin") + public Result edit(@RequestBody @Validated(UpdateGroup.class) ModelProviderDTO modelProviderDTO) { + ModelProviderDTO resp = modelProviderService.edit(modelProviderDTO); + return new Result().ok(resp); + } + + @PostMapping("/delete") + @Operation(summary = "删除模型供应器") + @RequiresPermissions("sys:role:superAdmin") + @Parameter(name = "ids", description = "ID数组", required = true) + public Result delete(@RequestBody List ids) { + modelProviderService.delete(ids); + return new Result<>(); + } + +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderDTO.java index 605ed13a..317820d5 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderDTO.java @@ -8,29 +8,37 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Data; +import xiaozhi.common.validator.group.UpdateGroup; @Data @Schema(description = "模型供应器/商") public class ModelProviderDTO implements Serializable { - // - // @Schema(description = "主键") - // private Long id; + @Schema(description = "主键") + @NotBlank(message = "id不能为空", groups = UpdateGroup.class) + private String id; @Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)") + @NotBlank(message = "modelType不能为空") private String modelType; @Schema(description = "供应器类型") + @NotBlank(message = "providerCode不能为空") private String providerCode; @Schema(description = "供应器名称") + @NotBlank(message = "name不能为空") private String name; @Schema(description = "供应器字段列表(JSON格式)") @TableField(typeHandler = JacksonTypeHandler.class) + @NotBlank(message = "fields(JSON格式)不能为空") private String fields; @Schema(description = "排序") + @NotNull(message = "sort不能为空") private Integer sort; @Schema(description = "更新者") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelProviderEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelProviderEntity.java index 62e84f67..00904759 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelProviderEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelProviderEntity.java @@ -3,10 +3,8 @@ package xiaozhi.modules.model.entity; import java.util.Date; import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -30,7 +28,6 @@ public class ModelProviderEntity { private String name; @Schema(description = "供应器字段列表(JSON格式)") - @TableField(typeHandler = JacksonTypeHandler.class) private String fields; @Schema(description = "排序") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java index b8cc38b1..9d15ac6d 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java @@ -28,4 +28,21 @@ public interface ModelConfigService extends BaseService { * @return 模型名称 */ String getModelNameById(String id); + + /** + * 根据ID获取模型配置 + * + * @param id 模型ID + * @param isCache 是否缓存 + * @return 模型配置实体 + */ + ModelConfigEntity getModelById(String id, boolean isCache); + + /** + * 设置默认模型 + * + * @param modelType 模型类型 + * @param isDefault 是否默认 + */ + void setDefaultModel(String modelType, int isDefault); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java index 2ace101e..d41766f6 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java @@ -2,8 +2,8 @@ package xiaozhi.modules.model.service; import java.util.List; +import xiaozhi.common.page.PageData; import xiaozhi.modules.model.dto.ModelProviderDTO; -import xiaozhi.modules.model.entity.ModelProviderEntity; public interface ModelProviderService { @@ -11,11 +11,15 @@ public interface ModelProviderService { List getListByModelType(String modelType); - ModelProviderDTO add(ModelProviderEntity modelProviderEntity); + ModelProviderDTO add(ModelProviderDTO modelProviderDTO); - ModelProviderDTO edit(ModelProviderEntity modelProviderEntity); + ModelProviderDTO edit(ModelProviderDTO modelProviderDTO); - void delete(); + void delete(String id); + + void delete(List id); + + PageData getListPage(ModelProviderDTO modelProviderDTO, String page, String limit); List getList(String modelType, String provideCode); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java index de8cbd4f..1f006d93 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java @@ -3,6 +3,7 @@ package xiaozhi.modules.model.service.impl; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @@ -19,6 +20,8 @@ import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.utils.ConvertUtils; +import xiaozhi.modules.agent.dao.AgentDao; +import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.model.dao.ModelConfigDao; import xiaozhi.modules.model.dto.ModelBasicInfoDTO; import xiaozhi.modules.model.dto.ModelConfigBodyDTO; @@ -36,12 +39,14 @@ public class ModelConfigServiceImpl extends BaseServiceImpl getModelCodeList(String modelType, String modelName) { List entities = modelConfigDao.selectList( new QueryWrapper() .eq("model_type", modelType) + .eq("is_enabled", 1) .like(StringUtils.isNotBlank(modelName), "model_name", "%" + modelName + "%") .select("id", "model_name")); return ConvertUtils.sourceToTarget(entities, ModelBasicInfoDTO.class); @@ -74,6 +79,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl agents = agentDao.selectList( + new QueryWrapper() + .eq("vad_model_id", modelId) + .or() + .eq("asr_model_id", modelId) + .or() + .eq("llm_model_id", modelId) + .or() + .eq("tts_model_id", modelId) + .or() + .eq("mem_model_id", modelId) + .or() + .eq("intent_model_id", modelId)); + if (!agents.isEmpty()) { + String agentNames = agents.stream() + .map(AgentEntity::getAgentName) + .collect(Collectors.joining("、")); + throw new RenException(String.format("该模型配置已被智能体[%s]引用,无法删除", agentNames)); + } + } + + /** + * 检查意图识别配置是否有引用 + * + * @param modelId 模型ID + */ + private void checkIntentConfigReference(String modelId) { + ModelConfigEntity modelConfig = modelConfigDao.selectById(modelId); + if (modelConfig != null + && "LLM".equals(modelConfig.getModelType() == null ? null : modelConfig.getModelType().toUpperCase())) { + List intentConfigs = modelConfigDao.selectList( + new QueryWrapper() + .eq("model_type", "Intent") + .like("config_json", "%" + modelId + "%")); + if (!intentConfigs.isEmpty()) { + throw new RenException("该LLM模型已被意图识别配置引用,无法删除"); + } + } + } + @Override public String getModelNameById(String id) { if (StringUtils.isBlank(id)) { @@ -125,4 +188,30 @@ public class ModelConfigServiceImpl extends BaseServiceImpl() + .eq("model_type", modelType)); + } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java index fb06e4d5..ffeec288 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java @@ -1,19 +1,29 @@ package xiaozhi.modules.model.service.impl; +import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import cn.hutool.json.JSONArray; import lombok.AllArgsConstructor; +import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.RenException; +import xiaozhi.common.page.PageData; import xiaozhi.common.service.impl.BaseServiceImpl; +import xiaozhi.common.user.UserDetail; import xiaozhi.common.utils.ConvertUtils; import xiaozhi.modules.model.dao.ModelProviderDao; import xiaozhi.modules.model.dto.ModelProviderDTO; import xiaozhi.modules.model.entity.ModelProviderEntity; import xiaozhi.modules.model.service.ModelProviderService; +import xiaozhi.modules.security.user.SecurityUser; @Service @AllArgsConstructor @@ -32,18 +42,78 @@ public class ModelProviderServiceImpl extends BaseServiceImpl getListPage(ModelProviderDTO modelProviderDTO, String page, String limit) { + + Map params = new HashMap(); + params.put(Constant.PAGE, page); + params.put(Constant.LIMIT, limit); + params.put(Constant.ORDER_FIELD, List.of("model_type", "sort")); + params.put(Constant.ORDER, "asc"); + + IPage pageParam = getPage(params, null, true); + + QueryWrapper wrapper = new QueryWrapper(); + + if (StringUtils.isNotBlank(modelProviderDTO.getModelType())) { + wrapper.eq("model_type", modelProviderDTO.getModelType()); + } + + if (StringUtils.isNotBlank(modelProviderDTO.getName())) { + wrapper.and(w -> w.like("name", modelProviderDTO.getName()) + .or() + .like("provider_code", modelProviderDTO.getName())); + } + return getPageData(modelProviderDao.selectPage(pageParam, wrapper), ModelProviderDTO.class); + } + + public static void main(String[] args) { + String jsonString = "\"[]\""; + JSONArray jsonArray = new JSONArray(jsonString); + System.out.println("字符串转 JSONArray: " + jsonArray.toString()); } @Override - public ModelProviderDTO edit(ModelProviderEntity modelProviderEntity) { - return null; + public ModelProviderDTO add(ModelProviderDTO modelProviderDTO) { + UserDetail user = SecurityUser.getUser(); + modelProviderDTO.setCreator(user.getId()); + modelProviderDTO.setUpdater(user.getId()); + modelProviderDTO.setCreateDate(new Date()); + modelProviderDTO.setUpdateDate(new Date()); + // 去除Fields左右的双引号 + + modelProviderDTO.setFields(modelProviderDTO.getFields()); + ModelProviderEntity entity = ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class); + if (modelProviderDao.insert(entity) == 0) { + throw new RenException("新增数据失败"); + } + + return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class); } @Override - public void delete() { + public ModelProviderDTO edit(ModelProviderDTO modelProviderDTO) { + UserDetail user = SecurityUser.getUser(); + modelProviderDTO.setUpdater(user.getId()); + modelProviderDTO.setUpdateDate(new Date()); + if (modelProviderDao + .updateById(ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class)) == 0) { + throw new RenException("修改数据失败"); + } + return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class); + } + @Override + public void delete(String id) { + if (modelProviderDao.deleteById(id) == 0) { + throw new RenException("删除数据失败"); + } + } + + @Override + public void delete(List ids) { + if (modelProviderDao.deleteBatchIds(ids) == 0) { + throw new RenException("删除数据失败"); + } } @Override diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java index e5b89c99..c015240f 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java @@ -18,6 +18,8 @@ import org.springframework.context.annotation.Configuration; import jakarta.servlet.Filter; import xiaozhi.modules.security.oauth2.Oauth2Filter; import xiaozhi.modules.security.oauth2.Oauth2Realm; +import xiaozhi.modules.security.secret.ServerSecretFilter; +import xiaozhi.modules.sys.service.SysParamsService; /** * Shiro的配置文件 @@ -46,7 +48,7 @@ public class ShiroConfig { } @Bean("shiroFilter") - public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { + public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager, SysParamsService sysParamsService) { ShiroFilterConfiguration config = new ShiroFilterConfiguration(); config.setFilterOncePerRequest(true); @@ -54,9 +56,11 @@ public class ShiroConfig { shiroFilter.setSecurityManager(securityManager); shiroFilter.setShiroFilterConfiguration(config); - // oauth过滤 Map filters = new HashMap<>(); + // oauth过滤 filters.put("oauth2", new Oauth2Filter()); + // 服务密钥过滤 + filters.put("server", new ServerSecretFilter(sysParamsService)); shiroFilter.setFilters(filters); // 添加Shiro的内置过滤器 @@ -69,15 +73,23 @@ public class ShiroConfig { */ Map filterMap = new LinkedHashMap<>(); filterMap.put("/ota/**", "anon"); + filterMap.put("/otaMag/download/**", "anon"); filterMap.put("/webjars/**", "anon"); filterMap.put("/druid/**", "anon"); filterMap.put("/v3/api-docs/**", "anon"); filterMap.put("/doc.html", "anon"); filterMap.put("/favicon.ico", "anon"); filterMap.put("/user/captcha", "anon"); + filterMap.put("/user/smsVerification", "anon"); filterMap.put("/user/login", "anon"); + filterMap.put("/user/pub-config", "anon"); filterMap.put("/user/register", "anon"); - filterMap.put("/device/register", "anon"); + filterMap.put("/user/retrieve-password", "anon"); + // 将config路径使用server服务过滤器 + filterMap.put("/config/**", "server"); + filterMap.put("/agent/chat-history/report", "server"); + filterMap.put("/agent/saveMemory/**", "server"); + filterMap.put("/agent/play/**", "anon"); filterMap.put("/**", "oauth2"); shiroFilter.setFilterChainDefinitionMap(filterMap); @@ -95,4 +107,4 @@ public class ShiroConfig { advisor.setSecurityManager(securityManager); return advisor; } -} \ No newline at end of file +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/config/WebMvcConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/security/config/WebMvcConfig.java index 7cfb03d9..02538caf 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/config/WebMvcConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/config/WebMvcConfig.java @@ -1,5 +1,6 @@ package xiaozhi.modules.security.config; +import java.text.SimpleDateFormat; import java.util.List; import java.util.TimeZone; @@ -18,6 +19,15 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; + +import xiaozhi.common.utils.DateUtils; @Configuration public class WebMvcConfig implements WebMvcConfigurer { @@ -33,11 +43,15 @@ public class WebMvcConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List> converters) { + // 特殊用途的转换器 converters.add(new ByteArrayHttpMessageConverter()); - converters.add(new StringHttpMessageConverter()); converters.add(new ResourceHttpMessageConverter()); - converters.add(new AllEncompassingFormHttpMessageConverter()); + + // 通用转换器 converters.add(new StringHttpMessageConverter()); + converters.add(new AllEncompassingFormHttpMessageConverter()); + + // JSON 转换器 converters.add(jackson2HttpMessageConverter()); } @@ -49,10 +63,29 @@ public class WebMvcConfig implements WebMvcConfigurer { // 忽略未知属性 mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - // 日期格式转换 - // mapper.setDateFormat(new SimpleDateFormat(DateUtils.DATE_TIME_PATTERN)); + // 设置时区 mapper.setTimeZone(TimeZone.getTimeZone("GMT+8")); + // 配置Java8日期时间序列化 + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(java.time.LocalDateTime.class, new LocalDateTimeSerializer( + java.time.format.DateTimeFormatter.ofPattern(DateUtils.DATE_TIME_PATTERN))); + javaTimeModule.addSerializer(java.time.LocalDate.class, new LocalDateSerializer( + java.time.format.DateTimeFormatter.ofPattern(DateUtils.DATE_PATTERN))); + javaTimeModule.addSerializer(java.time.LocalTime.class, + new LocalTimeSerializer(java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss"))); + javaTimeModule.addDeserializer(java.time.LocalDateTime.class, new LocalDateTimeDeserializer( + java.time.format.DateTimeFormatter.ofPattern(DateUtils.DATE_TIME_PATTERN))); + javaTimeModule.addDeserializer(java.time.LocalDate.class, new LocalDateDeserializer( + java.time.format.DateTimeFormatter.ofPattern(DateUtils.DATE_PATTERN))); + javaTimeModule.addDeserializer(java.time.LocalTime.class, + new LocalTimeDeserializer(java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss"))); + mapper.registerModule(javaTimeModule); + + // 配置java.util.Date的序列化和反序列化 + SimpleDateFormat dateFormat = new SimpleDateFormat(DateUtils.DATE_TIME_PATTERN); + mapper.setDateFormat(dateFormat); + // Long类型转String类型 SimpleModule simpleModule = new SimpleModule(); simpleModule.addSerializer(Long.class, ToStringSerializer.instance); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java b/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java index 7db612b5..0215cf7e 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java @@ -1,6 +1,10 @@ package xiaozhi.modules.security.controller; import java.io.IOException; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -13,6 +17,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; import lombok.AllArgsConstructor; +import xiaozhi.common.constant.Constant; import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.RenException; import xiaozhi.common.page.TokenDTO; @@ -21,13 +26,18 @@ import xiaozhi.common.utils.Result; import xiaozhi.common.validator.AssertUtils; import xiaozhi.common.validator.ValidatorUtils; import xiaozhi.modules.security.dto.LoginDTO; +import xiaozhi.modules.security.dto.SmsVerificationDTO; import xiaozhi.modules.security.password.PasswordUtils; import xiaozhi.modules.security.service.CaptchaService; import xiaozhi.modules.security.service.SysUserTokenService; import xiaozhi.modules.security.user.SecurityUser; import xiaozhi.modules.sys.dto.PasswordDTO; +import xiaozhi.modules.sys.dto.RetrievePasswordDTO; import xiaozhi.modules.sys.dto.SysUserDTO; +import xiaozhi.modules.sys.service.SysDictDataService; +import xiaozhi.modules.sys.service.SysParamsService; import xiaozhi.modules.sys.service.SysUserService; +import xiaozhi.modules.sys.vo.SysDictDataItem; /** * 登录控制层 @@ -40,24 +50,43 @@ public class LoginController { private final SysUserService sysUserService; private final SysUserTokenService sysUserTokenService; private final CaptchaService captchaService; + private final SysParamsService sysParamsService; + private final SysDictDataService sysDictDataService; @GetMapping("/captcha") @Operation(summary = "验证码") public void captcha(HttpServletResponse response, String uuid) throws IOException { // uuid不能为空 AssertUtils.isBlank(uuid, ErrorCode.IDENTIFIER_NOT_NULL); - // 生成验证码 captchaService.create(response, uuid); } + @PostMapping("/smsVerification") + @Operation(summary = "短信验证码") + public Result smsVerification(@RequestBody SmsVerificationDTO dto) { + // 验证图形验证码 + boolean validate = captchaService.validate(dto.getCaptchaId(), dto.getCaptcha(), true); + if (!validate) { + throw new RenException("图形验证码错误"); + } + Boolean isMobileRegister = sysParamsService + .getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class); + if (!isMobileRegister) { + throw new RenException("没有开启手机注册,没法使用短信验证码功能"); + } + // 发送短信验证码 + captchaService.sendSMSValidateCode(dto.getPhone()); + return new Result<>(); + } + @PostMapping("/login") @Operation(summary = "登录") public Result login(@RequestBody LoginDTO login) { // 验证是否正确输入验证码 - boolean validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha()); + boolean validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha(), true); if (!validate) { - throw new RenException("验证码错误,请重新获取"); + throw new RenException("图形验证码错误,请重新获取"); } // 按照用户名获取用户 SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername()); @@ -75,11 +104,32 @@ public class LoginController { @PostMapping("/register") @Operation(summary = "注册") public Result register(@RequestBody LoginDTO login) { - // 验证是否正确输入验证码 - boolean validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha()); - if (!validate) { - throw new RenException("验证码错误,请重新获取"); + if (!sysUserService.getAllowUserRegister()) { + throw new RenException("当前不允许普通用户注册"); } + // 是否开启手机注册 + Boolean isMobileRegister = sysParamsService + .getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class); + boolean validate; + if (isMobileRegister) { + // 验证用户是否是手机号码 + boolean validPhone = ValidatorUtils.isValidPhone(login.getUsername()); + if (!validPhone) { + throw new RenException("用户名不是手机号码,请重新输入"); + } + // 验证短信验证码是否正常 + validate = captchaService.validateSMSValidateCode(login.getUsername(), login.getMobileCaptcha(), false); + if (!validate) { + throw new RenException("手机验证码错误,请重新获取"); + } + } else { + // 验证是否正确输入验证码 + validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha(), true); + if (!validate) { + throw new RenException("图形验证码错误,请重新获取"); + } + } + // 按照用户名获取用户 SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername()); if (userDTO != null) { @@ -90,7 +140,6 @@ public class LoginController { userDTO.setPassword(login.getPassword()); sysUserService.save(userDTO); return new Result<>(); - } @GetMapping("/info") @@ -111,4 +160,55 @@ public class LoginController { sysUserTokenService.changePassword(userId, passwordDTO); return new Result<>(); } + + @PutMapping("/retrieve-password") + @Operation(summary = "找回密码") + public Result retrievePassword(@RequestBody RetrievePasswordDTO dto) { + // 是否开启手机注册 + Boolean isMobileRegister = sysParamsService + .getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class); + if (!isMobileRegister) { + throw new RenException("没有开启手机注册,没法使用找回密码功能"); + } + // 判断非空 + ValidatorUtils.validateEntity(dto); + // 验证用户是否是手机号码 + boolean validPhone = ValidatorUtils.isValidPhone(dto.getPhone()); + if (!validPhone) { + throw new RenException("输入的手机号码格式不正确"); + } + + // 按照用户名获取用户 + SysUserDTO userDTO = sysUserService.getByUsername(dto.getPhone()); + if (userDTO == null) { + throw new RenException("输入的手机号码未注册"); + } + // 验证短信验证码是否正常 + boolean validate = captchaService.validateSMSValidateCode(dto.getPhone(), dto.getCode(), false); + // 判断是否通过验证 + if (!validate) { + throw new RenException("输入的手机验证码错误"); + } + + sysUserService.changePasswordDirectly(userDTO.getId(), dto.getPassword()); + return new Result<>(); + } + + @GetMapping("/pub-config") + @Operation(summary = "公共配置") + public Result> pubConfig() { + Map config = new HashMap<>(); + config.put("enableMobileRegister", sysParamsService + .getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class)); + config.put("version", Constant.VERSION); + config.put("year", "©" + Calendar.getInstance().get(Calendar.YEAR)); + config.put("allowUserRegister", sysUserService.getAllowUserRegister()); + List list = sysDictDataService.getDictDataByType(Constant.DictType.MOBILE_AREA.getValue()); + config.put("mobileAreaList", list); + config.put("beianIcpNum", sysParamsService.getValue(Constant.SysBaseParam.BEIAN_ICP_NUM.getValue(), true)); + config.put("beianGaNum", sysParamsService.getValue(Constant.SysBaseParam.BEIAN_GA_NUM.getValue(), true)); + config.put("name", sysParamsService.getValue(Constant.SysBaseParam.SERVER_NAME.getValue(), true)); + + return new Result>().ok(config); + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/dto/LoginDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/security/dto/LoginDTO.java index cab143be..12b99d32 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/dto/LoginDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/dto/LoginDTO.java @@ -25,6 +25,9 @@ public class LoginDTO implements Serializable { @NotBlank(message = "{sysuser.captcha.require}") private String captcha; + @Schema(description = "手机验证码") + private String mobileCaptcha; + @Schema(description = "唯一标识") @NotBlank(message = "{sysuser.uuid.require}") private String captchaId; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/dto/SmsVerificationDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/security/dto/SmsVerificationDTO.java new file mode 100644 index 00000000..425ef059 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/dto/SmsVerificationDTO.java @@ -0,0 +1,28 @@ +package xiaozhi.modules.security.dto; + +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 短信验证码请求DTO + */ +@Data +@Schema(description = "短信验证码请求") +public class SmsVerificationDTO implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "手机号码") + @NotBlank(message = "{sysuser.username.require}") + private String phone; + + @Schema(description = "验证码") + @NotBlank(message = "{sysuser.captcha.require}") + private String captcha; + + @Schema(description = "唯一标识") + @NotBlank(message = "{sysuser.uuid.require}") + private String captchaId; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/secret/ServerSecretFilter.java b/main/manager-api/src/main/java/xiaozhi/modules/security/secret/ServerSecretFilter.java new file mode 100644 index 00000000..b2a18547 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/secret/ServerSecretFilter.java @@ -0,0 +1,105 @@ +package xiaozhi.modules.security.secret; + +import java.io.IOException; + +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.web.filter.authc.AuthenticatingFilter; +import org.springframework.web.bind.annotation.RequestMethod; + +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.ErrorCode; +import xiaozhi.common.utils.HttpContextUtils; +import xiaozhi.common.utils.JsonUtils; +import xiaozhi.common.utils.Result; +import xiaozhi.modules.sys.service.SysParamsService; + +/** + * Config API 过滤器 + */ +@Slf4j +@RequiredArgsConstructor +public class ServerSecretFilter extends AuthenticatingFilter { + private final SysParamsService sysParamsService; + + @Override + protected ServerSecretToken createToken(ServletRequest request, ServletResponse response) { + // 获取请求token + String token = getRequestToken((HttpServletRequest) request); + + if (StringUtils.isBlank(token)) { + log.warn("createToken:token is empty"); + return null; + } + + return new ServerSecretToken(token); + } + + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { + // 对OPTIONS请求放行 + if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) { + return true; + } + return false; + } + + @Override + protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { + // 获取token并校验 + String token = getRequestToken((HttpServletRequest) servletRequest); + if (StringUtils.isBlank(token)) { + // token为空,返回401 + this.sendUnauthorizedResponse((HttpServletResponse) servletResponse, "服务器密钥不能为空"); + return false; + } + + // 验证token是否匹配 + String serverSecret = getServerSecret(); + if (StringUtils.isBlank(serverSecret) || !serverSecret.equals(token)) { + // token无效,返回401 + this.sendUnauthorizedResponse((HttpServletResponse) servletResponse, "无效的服务器密钥"); + return false; + } + + return true; + } + + /** + * 发送未授权响应 + */ + private void sendUnauthorizedResponse(HttpServletResponse response, String message) { + response.setContentType("application/json;charset=utf-8"); + response.setHeader("Access-Control-Allow-Credentials", "true"); + response.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin()); + + try { + String json = JsonUtils.toJsonString(new Result().error(ErrorCode.UNAUTHORIZED, message)); + response.getWriter().print(json); + } catch (IOException e) { + log.error("响应输出失败", e); + } + } + + /** + * 获取请求的token + */ + private String getRequestToken(HttpServletRequest httpRequest) { + String token = null; + // 从header中获取token + String authorization = httpRequest.getHeader("Authorization"); + if (StringUtils.isNotBlank(authorization) && authorization.startsWith("Bearer ")) { + token = authorization.replace("Bearer ", ""); + } + return token; + } + + private String getServerSecret() { + return sysParamsService.getValue(Constant.SERVER_SECRET, true); + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/secret/ServerSecretToken.java b/main/manager-api/src/main/java/xiaozhi/modules/security/secret/ServerSecretToken.java new file mode 100644 index 00000000..9072eaf8 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/secret/ServerSecretToken.java @@ -0,0 +1,25 @@ +package xiaozhi.modules.security.secret; + +import org.apache.shiro.authc.AuthenticationToken; + +/** + * Config API Token + */ +public class ServerSecretToken implements AuthenticationToken { + private static final long serialVersionUID = 1L; + private final String token; + + public ServerSecretToken(String token) { + this.token = token; + } + + @Override + public Object getPrincipal() { + return token; + } + + @Override + public Object getCredentials() { + return token; + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java index 9e5c153f..0db9d296 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java @@ -18,10 +18,28 @@ public interface CaptchaService { /** * 验证码效验 - * - * @param uuid uuid - * @param code 验证码 + * + * @param uuid uuid + * @param code 验证码 + * @param delete 是否删除验证码 * @return true:成功 false:失败 */ - boolean validate(String uuid, String code); + boolean validate(String uuid, String code, Boolean delete); + + /** + * 发送短信验证码 + * + * @param phone 手机 + */ + void sendSMSValidateCode(String phone); + + /** + * 验证短信验证码 + * + * @param phone 手机 + * @param code 验证码 + * @param delete 是否删除验证码 + * @return true:成功 false:失败 + */ + boolean validateSMSValidateCode(String phone, String code, Boolean delete); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java index 012b4d3e..e823d70e 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java @@ -1,6 +1,7 @@ package xiaozhi.modules.security.service.impl; import java.io.IOException; +import java.util.Random; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; @@ -14,9 +15,13 @@ import com.wf.captcha.base.Captcha; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; +import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.RenException; import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; import xiaozhi.modules.security.service.CaptchaService; +import xiaozhi.modules.sms.service.SmsService; +import xiaozhi.modules.sys.service.SysParamsService; /** * 验证码 @@ -25,6 +30,10 @@ import xiaozhi.modules.security.service.CaptchaService; public class CaptchaServiceImpl implements CaptchaService { @Resource private RedisUtils redisUtils; + @Resource + private SmsService smsService; + @Resource + private SysParamsService sysParamsService; @Value("${renren.redis.open}") private boolean open; /** @@ -51,12 +60,12 @@ public class CaptchaServiceImpl implements CaptchaService { } @Override - public boolean validate(String uuid, String code) { + public boolean validate(String uuid, String code, Boolean delete) { if (StringUtils.isBlank(code)) { return false; } // 获取验证码 - String captcha = getCache(uuid); + String captcha = getCache(uuid, delete); // 效验成功 if (code.equalsIgnoreCase(captcha)) { @@ -66,21 +75,97 @@ public class CaptchaServiceImpl implements CaptchaService { return false; } + @Override + public void sendSMSValidateCode(String phone) { + // 检查发送间隔 + String lastSendTimeKey = RedisKeys.getSMSLastSendTimeKey(phone); + // 获取是否发送过,如果没有设置最后发送时间(60秒) + String lastSendTime = redisUtils + .getKeyOrCreate(lastSendTimeKey, + String.valueOf(System.currentTimeMillis()), 60L); + if (lastSendTime != null) { + long lastSendTimeLong = Long.parseLong(lastSendTime); + long currentTime = System.currentTimeMillis(); + long timeDiff = currentTime - lastSendTimeLong; + if (timeDiff < 60000) { + throw new RenException("发送太频繁,请" + (60000 - timeDiff) / 1000 + "秒后再试"); + } + } + + // 检查今日发送次数 + String todayCountKey = RedisKeys.getSMSTodayCountKey(phone); + Integer todayCount = (Integer) redisUtils.get(todayCountKey); + if (todayCount == null) { + todayCount = 0; + } + + // 获取最大发送次数限制 + Integer maxSendCount = sysParamsService.getValueObject( + Constant.SysMSMParam.SERVER_SMS_MAX_SEND_COUNT.getValue(), + Integer.class); + if (maxSendCount == null) { + maxSendCount = 5; // 默认值 + } + + if (todayCount >= maxSendCount) { + throw new RenException("今日发送次数已达上限"); + } + + String key = RedisKeys.getSMSValidateCodeKey(phone); + String validateCodes = generateValidateCode(6); + + // 设置验证码 + setCache(key, validateCodes); + + // 更新今日发送次数 + if (todayCount == 0) { + redisUtils.increment(todayCountKey, RedisUtils.DEFAULT_EXPIRE); + } else { + redisUtils.increment(todayCountKey); + } + + // 发送验证码短信 + smsService.sendVerificationCodeSms(phone, validateCodes); + } + + @Override + public boolean validateSMSValidateCode(String phone, String code, Boolean delete) { + String key = RedisKeys.getSMSValidateCodeKey(phone); + return validate(key, code, delete); + } + + /** + * 生成指定数量的随机数验证码 + * + * @param length 数量 + * @return 随机码 + */ + private String generateValidateCode(Integer length) { + String chars = "0123456789"; // 字符范围可以自定义:数字 + Random random = new Random(); + StringBuilder code = new StringBuilder(); + for (int i = 0; i < length; i++) { + code.append(chars.charAt(random.nextInt(chars.length()))); + } + return code.toString(); + } + private void setCache(String key, String value) { if (open) { key = RedisKeys.getCaptchaKey(key); + // 设置5分钟过期 redisUtils.set(key, value, 300); } else { localCache.put(key, value); } } - private String getCache(String key) { + private String getCache(String key, Boolean delete) { if (open) { key = RedisKeys.getCaptchaKey(key); String captcha = (String) redisUtils.get(key); // 删除验证码 - if (captcha != null) { + if (captcha != null && delete) { redisUtils.delete(key); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sms/service/SmsService.java b/main/manager-api/src/main/java/xiaozhi/modules/sms/service/SmsService.java new file mode 100644 index 00000000..c33ad945 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sms/service/SmsService.java @@ -0,0 +1,17 @@ +package xiaozhi.modules.sms.service; + +/** + * 短信服务的方法定义接口 + * + * @author zjy + * @since 2025-05-12 + */ +public interface SmsService { + + /** + * 发送验证码短信 + * @param phone 手机号码 + * @param VerificationCode 验证码 + */ + void sendVerificationCodeSms(String phone, String VerificationCode) ; +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sms/service/imp/ALiYunSmsService.java b/main/manager-api/src/main/java/xiaozhi/modules/sms/service/imp/ALiYunSmsService.java new file mode 100644 index 00000000..e0ebb523 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sms/service/imp/ALiYunSmsService.java @@ -0,0 +1,76 @@ +package xiaozhi.modules.sms.service.imp; + +import com.aliyun.dysmsapi20170525.Client; +import com.aliyun.dysmsapi20170525.models.SendSmsRequest; +import com.aliyun.dysmsapi20170525.models.SendSmsResponse; +import com.aliyun.teaopenapi.models.Config; +import com.aliyun.teautil.models.RuntimeOptions; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.RenException; +import xiaozhi.common.redis.RedisKeys; +import xiaozhi.common.redis.RedisUtils; +import xiaozhi.modules.sms.service.SmsService; +import xiaozhi.modules.sys.service.SysParamsService; + +@Service +@AllArgsConstructor +@Slf4j +public class ALiYunSmsService implements SmsService { + private final SysParamsService sysParamsService; + private final RedisUtils redisUtils; + + @Override + public void sendVerificationCodeSms(String phone, String VerificationCode) { + Client client = createClient(); + String SignName = sysParamsService.getValue(Constant.SysMSMParam + .ALIYUN_SMS_SIGN_NAME.getValue(),true); + String TemplateCode = sysParamsService.getValue(Constant.SysMSMParam + .ALIYUN_SMS_SMS_CODE_TEMPLATE_CODE.getValue(),true); + try { + SendSmsRequest sendSmsRequest = new SendSmsRequest() + .setSignName(SignName) + .setTemplateCode(TemplateCode) + .setPhoneNumbers(phone) + .setTemplateParam(String.format("{\"code\":\"%s\"}", VerificationCode)); + RuntimeOptions runtime = new RuntimeOptions(); + // 复制代码运行请自行打印 API 的返回值 + SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime); + log.info("发送短信响应的requestID: {}", sendSmsResponse.getBody().getRequestId()); + } catch (Exception e) { + // 如果发送失败了退还这次发送数 + String todayCountKey = RedisKeys.getSMSTodayCountKey(phone); + redisUtils.delete(todayCountKey); + // 错误 message + log.error(e.getMessage()); + throw new RenException("短信发送失败"); + } + + } + + + /** + * 创建阿里云连接 + * @return 返回连接对象 + */ + private Client createClient(){ + String ACCESS_KEY_ID = sysParamsService.getValue(Constant.SysMSMParam + .ALIYUN_SMS_ACCESS_KEY_ID.getValue(),true); + String ACCESS_KEY_SECRET = sysParamsService.getValue(Constant.SysMSMParam + .ALIYUN_SMS_ACCESS_KEY_SECRET.getValue(),true); + try { + Config config = new Config() + .setAccessKeyId(ACCESS_KEY_ID) + .setAccessKeySecret(ACCESS_KEY_SECRET); + // 配置 Endpoint。中国站请使用dysmsapi.aliyuncs.com + config.endpoint = "dysmsapi.aliyuncs.com"; + return new Client(config); + }catch (Exception e){ + // 错误 message + log.error(e.getMessage()); + throw new RenException("短信连接建立失败"); + } + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/AdminController.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/AdminController.java index e202e1e4..2ccc1e6a 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/AdminController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/AdminController.java @@ -58,7 +58,6 @@ public class AdminController { dto.setLimit((String) params.get(Constant.LIMIT)); dto.setPage((String) params.get(Constant.PAGE)); ValidatorUtils.validateEntity(dto); - ValidatorUtils.validateEntity(dto); PageData page = sysUserService.page(dto); return new Result>().ok(page); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/ServerSideManageController.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/ServerSideManageController.java new file mode 100644 index 00000000..a9664ced --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/ServerSideManageController.java @@ -0,0 +1,120 @@ +package xiaozhi.modules.sys.controller; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.socket.WebSocketHttpHeaders; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import xiaozhi.common.annotation.LogOperation; +import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.RenException; +import xiaozhi.common.utils.Result; +import xiaozhi.modules.sys.dto.EmitSeverActionDTO; +import xiaozhi.modules.sys.dto.ServerActionPayloadDTO; +import xiaozhi.modules.sys.dto.ServerActionResponseDTO; +import xiaozhi.modules.sys.enums.ServerActionEnum; +import xiaozhi.modules.sys.service.SysParamsService; +import xiaozhi.modules.sys.utils.WebSocketClientManager; + +/** + * 服务端管理控制器 + */ +@RestController +@RequestMapping("admin/server") +@Tag(name = "服务端管理") +@AllArgsConstructor +public class ServerSideManageController { + private final SysParamsService sysParamsService; + private static final ObjectMapper objectMapper; + static { + objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + @Operation(summary = "获取Ws服务端列表") + @GetMapping("/server-list") + @RequiresPermissions("sys:role:superAdmin") + public Result> getWsServerList() { + String cachedWs = sysParamsService.getValue(Constant.SERVER_WEBSOCKET, true); + return new Result>().ok(Arrays.asList(cachedWs.split(";"))); + } + + @Operation(summary = "通知python服务端更新配置") + @PostMapping("/emit-action") + @LogOperation("通知python服务端更新配置") + @RequiresPermissions("sys:role:superAdmin") + public Result emitServerAction(@RequestBody @Valid EmitSeverActionDTO emitSeverActionDTO) { + if (emitSeverActionDTO.getAction() == null) { + throw new RenException("无效服务端操作"); + } + String wsText = sysParamsService.getValue(Constant.SERVER_WEBSOCKET, true); + if (StringUtils.isBlank(wsText)) { + throw new RenException("未配置服务端WebSocket地址"); + } + String targetWs = emitSeverActionDTO.getTargetWs(); + String[] wsList = wsText.split(";"); + // 找到需要发起的 + if (StringUtils.isBlank(targetWs) || !Arrays.asList(wsList).contains(targetWs)) { + throw new RenException("目标WebSocket地址不存在"); + } + return new Result().ok(emitServerActionByWs(targetWs, emitSeverActionDTO.getAction())); + } + + private Boolean emitServerActionByWs(String targetWsUri, ServerActionEnum actionEnum) { + if (StringUtils.isBlank(targetWsUri) || actionEnum == null) { + return false; + } + String serverSK = sysParamsService.getValue(Constant.SERVER_SECRET, true); + WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); + headers.add("device-id", UUID.randomUUID().toString()); + headers.add("client-id", UUID.randomUUID().toString()); + + try (WebSocketClientManager client = new WebSocketClientManager.Builder() + .connectTimeout(3, TimeUnit.SECONDS) + .maxSessionDuration(120, TimeUnit.SECONDS) + .uri(targetWsUri) + .headers(headers) + .build()) { + // 如果连接成功则发送一个json数据包并等待服务端响应 + client.sendJson( + ServerActionPayloadDTO.build( + actionEnum, + Map.of("secret", serverSK))); + // 等待服务端响应并持续监听信息 + client.listener((jsonText) -> { + if (StringUtils.isBlank(jsonText)) { + return false; + } + try { + ServerActionResponseDTO response = objectMapper.readValue(jsonText, ServerActionResponseDTO.class); + Boolean isSuccess = ServerActionResponseDTO.isSuccess(response); + return isSuccess; + } catch (JsonProcessingException e) { + return false; + } + }); + } catch (Exception e) { + // 捕获全部错误,由全局异常处理器返回 + throw new RenException("WebSocket连接失败或连接超时"); + } + return true; + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysDictDataController.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysDictDataController.java new file mode 100644 index 00000000..30187fe8 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysDictDataController.java @@ -0,0 +1,105 @@ +package xiaozhi.modules.sys.controller; + +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import xiaozhi.common.constant.Constant; +import xiaozhi.common.page.PageData; +import xiaozhi.common.utils.Result; +import xiaozhi.common.validator.ValidatorUtils; +import xiaozhi.modules.sys.dto.SysDictDataDTO; +import xiaozhi.modules.sys.service.SysDictDataService; +import xiaozhi.modules.sys.vo.SysDictDataItem; +import xiaozhi.modules.sys.vo.SysDictDataVO; + +/** + * 字典数据管理 + * + * @author czc + * @since 2025-04-30 + */ +@AllArgsConstructor +@RestController +@RequestMapping("/admin/dict/data") +@Tag(name = "字典数据管理") +public class SysDictDataController { + private final SysDictDataService sysDictDataService; + + @GetMapping("/page") + @Operation(summary = "分页查询字典数据") + @RequiresPermissions("sys:role:superAdmin") + @Parameters({ @Parameter(name = "dictTypeId", description = "字典类型ID", required = true), + @Parameter(name = "dictLabel", description = "数据标签"), @Parameter(name = "dictValue", description = "数据值"), + @Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true), + @Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true) }) + public Result> page(@Parameter(hidden = true) @RequestParam Map params) { + ValidatorUtils.validateEntity(params); + // 强制校验dictTypeId是否存在 + if (!params.containsKey("dictTypeId") || StringUtils.isEmpty(String.valueOf(params.get("dictTypeId")))) { + return new Result>().error("dictTypeId不能为空"); + } + + PageData page = sysDictDataService.page(params); + return new Result>().ok(page); + } + + @GetMapping("/{id}") + @Operation(summary = "获取字典数据详情") + @RequiresPermissions("sys:role:superAdmin") + public Result get(@PathVariable("id") Long id) { + SysDictDataVO vo = sysDictDataService.get(id); + return new Result().ok(vo); + } + + @PostMapping("/save") + @Operation(summary = "新增字典数据") + @RequiresPermissions("sys:role:superAdmin") + public Result save(@RequestBody SysDictDataDTO dto) { + ValidatorUtils.validateEntity(dto); + sysDictDataService.save(dto); + return new Result<>(); + } + + @PutMapping("/update") + @Operation(summary = "修改字典数据") + @RequiresPermissions("sys:role:superAdmin") + public Result update(@RequestBody SysDictDataDTO dto) { + ValidatorUtils.validateEntity(dto); + sysDictDataService.update(dto); + return new Result<>(); + } + + @PostMapping("/delete") + @Operation(summary = "删除字典数据") + @RequiresPermissions("sys:role:superAdmin") + @Parameter(name = "ids", description = "ID数组", required = true) + public Result delete(@RequestBody Long[] ids) { + sysDictDataService.delete(ids); + return new Result<>(); + } + + @GetMapping("/type/{dictType}") + @Operation(summary = "获取字典数据列表") + @RequiresPermissions("sys:role:normal") + public Result> getDictDataByType(@PathVariable("dictType") String dictType) { + List list = sysDictDataService.getDictDataByType(dictType); + return new Result>().ok(list); + } + +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysDictTypeController.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysDictTypeController.java new file mode 100644 index 00000000..f16409b0 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysDictTypeController.java @@ -0,0 +1,92 @@ +package xiaozhi.modules.sys.controller; + +import java.util.Map; + +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import xiaozhi.common.constant.Constant; +import xiaozhi.common.page.PageData; +import xiaozhi.common.utils.Result; +import xiaozhi.common.validator.ValidatorUtils; +import xiaozhi.modules.sys.dto.SysDictTypeDTO; +import xiaozhi.modules.sys.service.SysDictTypeService; +import xiaozhi.modules.sys.vo.SysDictTypeVO; + +/** + * 字典类型管理 + * + * @author czc + * @since 2025-04-30 + */ +@AllArgsConstructor +@RestController +@RequestMapping("/admin/dict/type") +@Tag(name = "字典类型管理") +public class SysDictTypeController { + private final SysDictTypeService sysDictTypeService; + + @GetMapping("/page") + @Operation(summary = "分页查询字典类型") + @RequiresPermissions("sys:role:superAdmin") + @Parameters({ @Parameter(name = "dictType", description = "字典类型编码"), + @Parameter(name = "dictName", description = "字典类型名称"), + @Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true), + @Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true) }) + public Result> page(@Parameter(hidden = true) @RequestParam Map params) { + ValidatorUtils.validateEntity(params); + PageData page = sysDictTypeService.page(params); + return new Result>().ok(page); + } + + @GetMapping("/{id}") + @Operation(summary = "获取字典类型详情") + @RequiresPermissions("sys:role:superAdmin") + public Result get(@PathVariable("id") Long id) { + SysDictTypeVO vo = sysDictTypeService.get(id); + return new Result().ok(vo); + } + + @PostMapping("/save") + @Operation(summary = "保存字典类型") + @RequiresPermissions("sys:role:superAdmin") + public Result save(@RequestBody SysDictTypeDTO dto) { + // 参数校验 + ValidatorUtils.validateEntity(dto); + + sysDictTypeService.save(dto); + return new Result<>(); + } + + @PutMapping("/update") + @Operation(summary = "修改字典类型") + @RequiresPermissions("sys:role:superAdmin") + public Result update(@RequestBody SysDictTypeDTO dto) { + // 参数校验 + ValidatorUtils.validateEntity(dto); + + sysDictTypeService.update(dto); + return new Result<>(); + } + + @PostMapping("/delete") + @Operation(summary = "删除字典类型") + @RequiresPermissions("sys:role:superAdmin") + @Parameter(name = "ids", description = "ID数组", required = true) + public Result delete(@RequestBody Long[] ids) { + sysDictTypeService.delete(ids); + return new Result<>(); + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java index 252f5602..4860cf1c 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java @@ -2,8 +2,10 @@ package xiaozhi.modules.sys.controller; import java.util.Map; +import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; -import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -12,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -21,6 +24,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import xiaozhi.common.annotation.LogOperation; import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.RenException; import xiaozhi.common.page.PageData; import xiaozhi.common.utils.Result; import xiaozhi.common.validator.AssertUtils; @@ -28,8 +32,10 @@ import xiaozhi.common.validator.ValidatorUtils; import xiaozhi.common.validator.group.AddGroup; import xiaozhi.common.validator.group.DefaultGroup; import xiaozhi.common.validator.group.UpdateGroup; +import xiaozhi.modules.config.service.ConfigService; import xiaozhi.modules.sys.dto.SysParamsDTO; import xiaozhi.modules.sys.service.SysParamsService; +import xiaozhi.modules.sys.utils.WebSocketValidator; /** * 参数管理 @@ -43,6 +49,8 @@ import xiaozhi.modules.sys.service.SysParamsService; @AllArgsConstructor public class SysParamsController { private final SysParamsService sysParamsService; + private final ConfigService configService; + private final RestTemplate restTemplate; @GetMapping("page") @Operation(summary = "分页") @@ -51,7 +59,7 @@ public class SysParamsController { @Parameter(name = Constant.LIMIT, description = "每页显示记录数", in = ParameterIn.QUERY, required = true, ref = "int"), @Parameter(name = Constant.ORDER_FIELD, description = "排序字段", in = ParameterIn.QUERY, ref = "String"), @Parameter(name = Constant.ORDER, description = "排序方式,可选值(asc、desc)", in = ParameterIn.QUERY, ref = "String"), - @Parameter(name = "paramCode", description = "参数编码", in = ParameterIn.QUERY, ref = "String") + @Parameter(name = "paramCode", description = "参数编码或参数备注", in = ParameterIn.QUERY, ref = "String") }) @RequiresPermissions("sys:role:superAdmin") public Result> page(@Parameter(hidden = true) @RequestParam Map params) { @@ -78,7 +86,7 @@ public class SysParamsController { ValidatorUtils.validateEntity(dto, AddGroup.class, DefaultGroup.class); sysParamsService.save(dto); - + configService.getConfig(false); return new Result(); } @@ -90,21 +98,101 @@ public class SysParamsController { // 效验数据 ValidatorUtils.validateEntity(dto, UpdateGroup.class, DefaultGroup.class); - sysParamsService.update(dto); + // 验证WebSocket地址列表 + validateWebSocketUrls(dto.getParamCode(), dto.getParamValue()); + // 验证OTA地址 + validateOtaUrl(dto.getParamCode(), dto.getParamValue()); + + sysParamsService.update(dto); + configService.getConfig(false); return new Result(); } - @DeleteMapping + /** + * 验证WebSocket地址列表 + * + * @param urls WebSocket地址列表,以分号分隔 + * @return 验证结果 + */ + private void validateWebSocketUrls(String paramCode, String urls) { + if (!paramCode.equals(Constant.SERVER_WEBSOCKET)) { + return; + } + String[] wsUrls = urls.split("\\;"); + if (wsUrls.length == 0) { + throw new RenException("WebSocket地址列表不能为空"); + } + for (String url : wsUrls) { + if (StringUtils.isNotBlank(url)) { + // 检查是否包含localhost或127.0.0.1 + if (url.contains("localhost") || url.contains("127.0.0.1")) { + throw new RenException("WebSocket地址不能使用localhost或127.0.0.1"); + } + + // 验证WebSocket地址格式 + if (!WebSocketValidator.validateUrlFormat(url)) { + throw new RenException("WebSocket地址格式不正确: " + url); + } + + // 测试WebSocket连接 + if (!WebSocketValidator.testConnection(url)) { + throw new RenException("WebSocket连接测试失败: " + url); + } + } + } + } + + @PostMapping("/delete") @Operation(summary = "删除") @LogOperation("删除") @RequiresPermissions("sys:role:superAdmin") - public Result delete(@RequestBody Long[] ids) { + public Result delete(@RequestBody String[] ids) { // 效验数据 AssertUtils.isArrayEmpty(ids, "id"); sysParamsService.delete(ids); - + configService.getConfig(false); return new Result(); } + + /** + * 验证OTA地址 + */ + private void validateOtaUrl(String paramCode, String url) { + if (!paramCode.equals(Constant.SERVER_OTA)) { + return; + } + if (StringUtils.isBlank(url) || url.equals("null")) { + throw new RenException("OTA地址不能为空"); + } + + // 检查是否包含localhost或127.0.0.1 + if (url.contains("localhost") || url.contains("127.0.0.1")) { + throw new RenException("OTA地址不能使用localhost或127.0.0.1"); + } + + // 验证URL格式 + if (!url.toLowerCase().startsWith("http")) { + throw new RenException("OTA地址必须以http或https开头"); + } + if (!url.endsWith("/ota/")) { + throw new RenException("OTA地址必须以/ota/结尾"); + } + + try { + // 发送GET请求 + ResponseEntity response = restTemplate.getForEntity(url, String.class); + if (response.getStatusCode() != HttpStatus.OK) { + throw new RenException("OTA接口访问失败,状态码:" + response.getStatusCode()); + } + // 检查响应内容是否包含OTA相关信息 + String body = response.getBody(); + if (body == null || !body.contains("OTA")) { + throw new RenException("OTA接口返回内容格式不正确,可能不是一个真实的OTA接口"); + } + } catch (Exception e) { + throw new RenException("OTA接口验证失败:" + e.getMessage()); + } + } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictDataDao.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictDataDao.java index 9dd124fe..e5c4c7bb 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictDataDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictDataDao.java @@ -5,9 +5,8 @@ import java.util.List; import org.apache.ibatis.annotations.Mapper; import xiaozhi.common.dao.BaseDao; -import xiaozhi.modules.sys.dto.SysDictDataDTO; -import xiaozhi.modules.sys.entity.DictData; import xiaozhi.modules.sys.entity.SysDictDataEntity; +import xiaozhi.modules.sys.vo.SysDictDataItem; /** * 字典数据 @@ -15,10 +14,13 @@ import xiaozhi.modules.sys.entity.SysDictDataEntity; @Mapper public interface SysDictDataDao extends BaseDao { - /** - * 字典数据列表 - */ - List getDictDataList(); + List getDictDataByType(String dictType); - List getDataByTypeCode(String dictType); + /** + * 根据字典类型ID获取字典类型编码 + * + * @param dictTypeId 字典类型ID + * @return 字典类型编码 + */ + String getTypeByTypeId(Long dictTypeId); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictTypeDao.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictTypeDao.java index 5a9d5a27..6c271e31 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictTypeDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictTypeDao.java @@ -1,11 +1,8 @@ package xiaozhi.modules.sys.dao; -import java.util.List; - import org.apache.ibatis.annotations.Mapper; import xiaozhi.common.dao.BaseDao; -import xiaozhi.modules.sys.entity.DictType; import xiaozhi.modules.sys.entity.SysDictTypeEntity; /** @@ -14,9 +11,4 @@ import xiaozhi.modules.sys.entity.SysDictTypeEntity; @Mapper public interface SysDictTypeDao extends BaseDao { - /** - * 字典类型列表 - */ - List getDictTypeList(); - } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysParamsDao.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysParamsDao.java index d35d1445..6eebc8be 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysParamsDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysParamsDao.java @@ -27,7 +27,7 @@ public interface SysParamsDao extends BaseDao { * @param ids ids * @return 返回参数编码列表 */ - List getParamCodeList(Long[] ids); + List getParamCodeList(String[] ids); /** * 根据参数编码,更新value diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/EmitSeverActionDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/EmitSeverActionDTO.java new file mode 100644 index 00000000..888ef4ec --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/EmitSeverActionDTO.java @@ -0,0 +1,29 @@ +package xiaozhi.modules.sys.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import xiaozhi.modules.sys.enums.ServerActionEnum; + +/** + * 发送python服务端操作DTO + * + * @Author CAIXYPROMISE + * @since 2025/5/15 4:55 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class EmitSeverActionDTO +{ + @Schema(description = "目标ws地址") + @NotEmpty(message = "目标ws地址不能为空") + private String targetWs; + + @Schema(description = "指定操作") + @NotNull(message = "操作不能为空") + private ServerActionEnum action; +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/RetrievePasswordDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/RetrievePasswordDTO.java new file mode 100644 index 00000000..efac8099 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/RetrievePasswordDTO.java @@ -0,0 +1,30 @@ +package xiaozhi.modules.sys.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.io.Serializable; + +/** + * 找回密码DTO + */ +@Data +@Schema(description = "找回密码") +public class RetrievePasswordDTO implements Serializable { + + @Schema(description = "手机号码") + @NotBlank(message = "{sysuser.password.require}") + private String phone; + + @Schema(description = "验证码") + @NotBlank(message = "{sysuser.password.require}") + private String code; + + @Schema(description = "新密码") + @NotBlank(message = "{sysuser.password.require}") + private String password; + + + +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/ServerActionPayloadDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/ServerActionPayloadDTO.java new file mode 100644 index 00000000..cb158d25 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/ServerActionPayloadDTO.java @@ -0,0 +1,39 @@ +package xiaozhi.modules.sys.dto; + +import lombok.Data; +import xiaozhi.modules.sys.enums.ServerActionEnum; + +import java.util.Map; + +/** + * 服务端动作DTO + * + * @Author CAIXYPROMISE + * @since 2025/5/15 5:10 + */ +@Data +public class ServerActionPayloadDTO +{ + /** + * 类型(智控台发往服务端的都是server) + */ + private String type; + /** + * 动作 + */ + private ServerActionEnum action; + /** + * 内容 + */ + private Map content; + + public static ServerActionPayloadDTO build(ServerActionEnum action, Map content) { + ServerActionPayloadDTO serverActionPayloadDTO = new ServerActionPayloadDTO(); + serverActionPayloadDTO.setAction(action); + serverActionPayloadDTO.setContent(content); + serverActionPayloadDTO.setType("server"); + return serverActionPayloadDTO; + } + // 私有化 + private ServerActionPayloadDTO() {} +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/ServerActionResponseDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/ServerActionResponseDTO.java new file mode 100644 index 00000000..6286b74a --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/ServerActionResponseDTO.java @@ -0,0 +1,34 @@ +package xiaozhi.modules.sys.dto; + +import lombok.Data; +import xiaozhi.modules.sys.enums.ServerActionResponseEnum; + +import java.util.Map; + +/** + * 服务端动作响应体 + */ +@Data +public class ServerActionResponseDTO +{ + private ServerActionResponseEnum status; + private String message; + private String type; + private Map content; // 后续这个字段可以移除,并把这个类作为基类,针对业务写自己的content类型 + public static final String DEFAULT_TYPE_FORM_SERVER = "server"; + + public static Boolean isSuccess(ServerActionResponseDTO actionResponseDTO) { + System.out.println(actionResponseDTO); + if (actionResponseDTO == null) { + return false; + } + if (actionResponseDTO.getStatus() == null || !actionResponseDTO.getStatus().equals(ServerActionResponseEnum.SUCCESS)) { + return false; + } + Object actionType = actionResponseDTO.getContent().get("action"); + if (actionType == null) { + return false; + } + return actionResponseDTO.getType() != null && actionResponseDTO.getType().equals(DEFAULT_TYPE_FORM_SERVER); + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictTypeDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictTypeDTO.java index 01d69795..912c817c 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictTypeDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictTypeDTO.java @@ -51,5 +51,6 @@ public class SysDictTypeDTO implements Serializable { @Schema(description = "更新时间") @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN) private Date updateDate; } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysParamsDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysParamsDTO.java index 7b4dd28d..a6f27b72 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysParamsDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysParamsDTO.java @@ -10,6 +10,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Null; +import jakarta.validation.constraints.Pattern; import lombok.Data; import xiaozhi.common.utils.DateUtils; import xiaozhi.common.validator.group.AddGroup; @@ -36,6 +37,11 @@ public class SysParamsDTO implements Serializable { @NotBlank(message = "{sysparams.paramvalue.require}", groups = DefaultGroup.class) private String paramValue; + @Schema(description = "值类型") + @NotBlank(message = "{sysparams.valuetype.require}", groups = DefaultGroup.class) + @Pattern(regexp = "^(string|number|boolean|array|json)$", message = "{sysparams.valuetype.pattern}", groups = DefaultGroup.class) + private String valueType; + @Schema(description = "备注") private String remark; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictData.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictData.java deleted file mode 100644 index c8deac81..00000000 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictData.java +++ /dev/null @@ -1,16 +0,0 @@ -package xiaozhi.modules.sys.entity; - -import com.fasterxml.jackson.annotation.JsonIgnore; - -import lombok.Data; - -/** - * 字典数据 - */ -@Data -public class DictData { - @JsonIgnore - private Long dictTypeId; - private String dictLabel; - private String dictValue; -} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictType.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictType.java deleted file mode 100644 index 4a1e2ca5..00000000 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictType.java +++ /dev/null @@ -1,19 +0,0 @@ -package xiaozhi.modules.sys.entity; - -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnore; - -import lombok.Data; - -/** - * 字典类型 - */ -@Data -public class DictType { - @JsonIgnore - private Long id; - private String dictType; - private List dataList = new ArrayList<>(); -} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictTypeEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictTypeEntity.java index fa372055..350d857b 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictTypeEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictTypeEntity.java @@ -18,7 +18,7 @@ import xiaozhi.common.entity.BaseEntity; @TableName("sys_dict_type") public class SysDictTypeEntity extends BaseEntity { /** - * 字典类型 + * 字典类型编码 */ private String dictType; /** diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysParamsEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysParamsEntity.java index 989800e2..c51c46fa 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysParamsEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysParamsEntity.java @@ -25,6 +25,10 @@ public class SysParamsEntity extends BaseEntity { * 参数值 */ private String paramValue; + /** + * 值类型:string-字符串,number-数字,boolean-布尔,array-数组 + */ + private String valueType; /** * 类型 0:系统参数 1:非系统参数 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionEnum.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionEnum.java new file mode 100644 index 00000000..33babf45 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionEnum.java @@ -0,0 +1,43 @@ +package xiaozhi.modules.sys.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import xiaozhi.common.exception.RenException; + +/** + * 服务端动作枚举 + * + * @Author CAIXYPROMISE + * @since 2025/5/15 4:57 + */ +public enum ServerActionEnum +{ + RESTART("restart"), + UPDATE_CONFIG("update_config"); + + private final String value; + + ServerActionEnum(String value) + { + this.value = value; + } + + @JsonValue + public String getValue() + { + return value; + } + + @JsonCreator + public static ServerActionEnum fromValue(String value) + { + for (ServerActionEnum action : ServerActionEnum.values()) + { + if (action.value.equalsIgnoreCase(value)) + { + return action; + } + } + return null; + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionResponseEnum.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionResponseEnum.java new file mode 100644 index 00000000..e1b54a66 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionResponseEnum.java @@ -0,0 +1,49 @@ +package xiaozhi.modules.sys.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +/** + * 服务端调用响应枚举 + * + * @Author CAIXYPROMISE + * @since 2025/5/15 5:50 + */ +public enum ServerActionResponseEnum +{ + SUCCESS("success"), FAIL("fail"); + private final String value; + + ServerActionResponseEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() + { + return value; + } + + @JsonCreator + public static ServerActionResponseEnum fromValue(String value) { + ServerActionResponseEnum byValue = getByValue(value); + if (byValue == null) { + throw new IllegalArgumentException("Unknown enum value: " + value); + } + return byValue; + } + + public static ServerActionResponseEnum getByValue(String value) { + if (StringUtils.isBlank(value)) { + return null; + } + for (ServerActionResponseEnum action : ServerActionResponseEnum.values()) { + if (action.value.equals(value)) { + return action; + } + } + return null; + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictDataService.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictDataService.java index a33df029..ea898447 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictDataService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictDataService.java @@ -1,25 +1,70 @@ package xiaozhi.modules.sys.service; +import java.util.List; import java.util.Map; import xiaozhi.common.page.PageData; import xiaozhi.common.service.BaseService; import xiaozhi.modules.sys.dto.SysDictDataDTO; import xiaozhi.modules.sys.entity.SysDictDataEntity; +import xiaozhi.modules.sys.vo.SysDictDataItem; +import xiaozhi.modules.sys.vo.SysDictDataVO; /** * 数据字典 */ public interface SysDictDataService extends BaseService { - PageData page(Map params); + /** + * 分页查询数据字典信息 + * + * @param params 查询参数,包含分页信息和查询条件 + * @return 返回数据字典的分页查询结果 + */ + PageData page(Map params); - SysDictDataDTO get(Long id); + /** + * 根据ID获取数据字典实体 + * + * @param id 数据字典实体的唯一标识 + * @return 返回数据字典实体的详细信息 + */ + SysDictDataVO get(Long id); + /** + * 保存新的数据字典项 + * + * @param dto 数据字典项的保存数据传输对象 + */ void save(SysDictDataDTO dto); + /** + * 更新数据字典项 + * + * @param dto 数据字典项的更新数据传输对象 + */ void update(SysDictDataDTO dto); + /** + * 删除数据字典项 + * + * @param ids 要删除的数据字典项的ID数组 + */ void delete(Long[] ids); + /** + * 根据字典类型ID删除对应的字典数据 + * + * @param dictTypeId 字典类型ID + */ + void deleteByTypeId(Long dictTypeId); + + /** + * 根据字典类型获取字典数据列表 + * + * @param dictType 字典类型 + * @return 返回字典数据列表 + */ + List getDictDataByType(String dictType); + } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictTypeService.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictTypeService.java index a18e086f..697b2d10 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictTypeService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictTypeService.java @@ -6,25 +6,55 @@ import java.util.Map; import xiaozhi.common.page.PageData; import xiaozhi.common.service.BaseService; import xiaozhi.modules.sys.dto.SysDictTypeDTO; -import xiaozhi.modules.sys.entity.DictType; import xiaozhi.modules.sys.entity.SysDictTypeEntity; +import xiaozhi.modules.sys.vo.SysDictTypeVO; /** * 数据字典 */ public interface SysDictTypeService extends BaseService { - PageData page(Map params); + /** + * 分页查询字典类型信息 + * + * @param params 查询参数,包含分页信息和查询条件 + * @return 返回分页的字典类型数据 + */ + PageData page(Map params); - SysDictTypeDTO get(Long id); + /** + * 根据ID获取字典类型信息 + * + * @param id 字典类型ID + * @return 返回字典类型对象 + */ + SysDictTypeVO get(Long id); + /** + * 保存字典类型信息 + * + * @param dto 字典类型数据传输对象 + */ void save(SysDictTypeDTO dto); + /** + * 更新字典类型信息 + * + * @param dto 字典类型数据传输对象 + */ void update(SysDictTypeDTO dto); + /** + * 删除字典类型信息 + * + * @param ids 要删除的字典类型ID数组 + */ void delete(Long[] ids); - List getAllList(); - - List getDictTypeList(); + /** + * 列出所有字典类型信息 + * + * @return 返回字典类型列表 + */ + List list(Map params); } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java index 7475f346..ac67a276 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java @@ -23,14 +23,15 @@ public interface SysParamsService extends BaseService { void update(SysParamsDTO dto); - void delete(Long[] ids); + void delete(String[] ids); /** * 根据参数编码,获取参数的value值 * * @param paramCode 参数编码 + * @param fromCache 是否从缓存中获取 */ - String getValue(String paramCode); + String getValue(String paramCode, Boolean fromCache); /** * 根据参数编码,获取value的Object对象 @@ -47,4 +48,9 @@ public interface SysParamsService extends BaseService { * @param paramValue 参数值 */ int updateValueByCode(String paramCode, String paramValue); + + /** + * 初始化服务器密钥 + */ + void initServerSecret(); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java index d5d103af..7a35da32 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java @@ -65,4 +65,11 @@ public interface SysUserService extends BaseService { * @param userIds 用户ID数组 */ void changeStatus(Integer status, String[] userIds); + + /** + * 获取是否允许用户注册 + * + * @return 是否允许用户注册 + */ + boolean getAllowUserRegister(); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java index 20530e6b..ffe88986 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java @@ -1,37 +1,55 @@ package xiaozhi.modules.sys.service.impl; -import java.util.Arrays; +import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.AllArgsConstructor; +import xiaozhi.common.exception.RenException; import xiaozhi.common.page.PageData; +import xiaozhi.common.redis.RedisKeys; +import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.utils.ConvertUtils; import xiaozhi.modules.sys.dao.SysDictDataDao; +import xiaozhi.modules.sys.dao.SysUserDao; import xiaozhi.modules.sys.dto.SysDictDataDTO; import xiaozhi.modules.sys.entity.SysDictDataEntity; +import xiaozhi.modules.sys.entity.SysUserEntity; import xiaozhi.modules.sys.service.SysDictDataService; +import xiaozhi.modules.sys.vo.SysDictDataItem; +import xiaozhi.modules.sys.vo.SysDictDataVO; /** * 字典类型 */ @Service +@AllArgsConstructor public class SysDictDataServiceImpl extends BaseServiceImpl implements SysDictDataService { + private final SysUserDao sysUserDao; + private final RedisUtils redisUtils; @Override - public PageData page(Map params) { - IPage page = baseDao.selectPage( - getPage(params, "sort", true), - getWrapper(params)); + public PageData page(Map params) { + IPage page = baseDao.selectPage(getPage(params, "sort", true), getWrapper(params)); - return getPageData(page, SysDictDataDTO.class); + PageData pageData = getPageData(page, SysDictDataVO.class); + + setUserName(pageData.getList()); + + return pageData; } private QueryWrapper getWrapper(Map params) { @@ -48,32 +66,118 @@ public class SysDictDataServiceImpl extends BaseServiceImpl wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysDictDataEntity::getDictTypeId, dictTypeId); + baseDao.delete(wrapper); + } + + /** + * 设置用户名 + * + * @param sysDictDataList 字典类型集合 + */ + private void setUserName(List sysDictDataList) { + // 收集所有用户 ID + Set userIds = sysDictDataList.stream().flatMap(vo -> Stream.of(vo.getCreator(), vo.getUpdater())) + .filter(Objects::nonNull).collect(Collectors.toSet()); + + // 设置更新者和创建者名称 + if (!userIds.isEmpty()) { + List sysUserEntities = sysUserDao.selectBatchIds(userIds); + // 把List转成Map,Map + Map userNameMap = sysUserEntities.stream().collect(Collectors.toMap(SysUserEntity::getId, + SysUserEntity::getUsername, (existing, replacement) -> existing)); + + sysDictDataList.forEach(vo -> { + vo.setCreatorName(userNameMap.get(vo.getCreator())); + vo.setUpdaterName(userNameMap.get(vo.getUpdater())); + }); + } + } + + private void checkDictValueUnique(Long dictTypeId, String dictValue, String excludeId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysDictDataEntity::getDictTypeId, dictTypeId).eq(SysDictDataEntity::getDictLabel, dictValue); + if (StringUtils.isNotBlank(excludeId)) { + queryWrapper.ne(SysDictDataEntity::getId, excludeId); + } + boolean exists = baseDao.exists(queryWrapper); + if (exists) { + throw new RenException("字典标签重复"); + } + } + + @Override + public List getDictDataByType(String dictType) { + if (StringUtils.isBlank(dictType)) { + return null; + } + + // 先从Redis获取缓存 + String key = RedisKeys.getDictDataByTypeKey(dictType); + List cachedData = (List) redisUtils.get(key); + if (cachedData != null) { + return cachedData; + } + + // 如果缓存中没有,则从数据库获取 + List data = baseDao.getDictDataByType(dictType); + + // 存入Redis缓存 + if (data != null) { + redisUtils.set(key, data); + } + + return data; } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictTypeServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictTypeServiceImpl.java index ce2e423e..91522482 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictTypeServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictTypeServiceImpl.java @@ -3,42 +3,52 @@ package xiaozhi.modules.sys.service.impl; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import lombok.AllArgsConstructor; +import xiaozhi.common.exception.RenException; import xiaozhi.common.page.PageData; import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.utils.ConvertUtils; -import xiaozhi.modules.sys.dao.SysDictDataDao; import xiaozhi.modules.sys.dao.SysDictTypeDao; +import xiaozhi.modules.sys.dao.SysUserDao; import xiaozhi.modules.sys.dto.SysDictTypeDTO; -import xiaozhi.modules.sys.entity.DictData; -import xiaozhi.modules.sys.entity.DictType; import xiaozhi.modules.sys.entity.SysDictTypeEntity; +import xiaozhi.modules.sys.entity.SysUserEntity; +import xiaozhi.modules.sys.service.SysDictDataService; import xiaozhi.modules.sys.service.SysDictTypeService; +import xiaozhi.modules.sys.vo.SysDictTypeVO; /** * 字典类型 */ -@AllArgsConstructor @Service +@AllArgsConstructor public class SysDictTypeServiceImpl extends BaseServiceImpl implements SysDictTypeService { - private final SysDictDataDao sysDictDataDao; + private final SysUserDao sysUserDao; + private final SysDictDataService sysDictDataService; @Override - public PageData page(Map params) { - IPage page = baseDao.selectPage( - getPage(params, "sort", true), - getWrapper(params)); + public PageData page(Map params) { + IPage page = baseDao.selectPage(getPage(params, "sort", true), getWrapper(params)); - return getPageData(page, SysDictTypeDTO.class); + PageData pageData = getPageData(page, SysDictTypeVO.class); + + setUserName(pageData.getList()); + + return pageData; } private QueryWrapper getWrapper(Map params) { @@ -53,15 +63,21 @@ public class SysDictTypeServiceImpl extends BaseServiceImpl getAllList() { - List typeList = baseDao.getDictTypeList(); - List dataList = sysDictDataDao.getDictDataList(); - for (DictType type : typeList) { - for (DictData data : dataList) { - if (type.getId().equals(data.getDictTypeId())) { - type.getDataList().add(data); - } - } + public List list(Map params) { + List entityList = baseDao.selectList(getWrapper(params)); + List sysDictTypeVOList = ConvertUtils.sourceToTarget(entityList, SysDictTypeVO.class); + // 设置用户名 + setUserName(sysDictTypeVOList); + + return sysDictTypeVOList; + } + + /** + * 设置用户名 + * + * @param sysDictTypeList 字典类型集合 + */ + private void setUserName(List sysDictTypeList) { + // 收集所有用户 ID + Set userIds = sysDictTypeList.stream().flatMap(vo -> Stream.of(vo.getCreator(), vo.getUpdater())) + .filter(Objects::nonNull).collect(Collectors.toSet()); + + // 设置更新者和创建者名称 + if (!userIds.isEmpty()) { + List sysUserEntities = sysUserDao.selectBatchIds(userIds); + // 把List转成Map,Map + Map userNameMap = sysUserEntities.stream().collect(Collectors.toMap(SysUserEntity::getId, + SysUserEntity::getUsername, (existing, replacement) -> existing)); + + sysDictTypeList.forEach(vo -> { + vo.setCreatorName(userNameMap.get(vo.getCreator())); + vo.setUpdaterName(userNameMap.get(vo.getUpdater())); + }); } - return typeList; } - @Override - public List getDictTypeList() { - return baseDao.getDictTypeList(); + private void checkDictTypeUnique(String dictType, String excludeId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysDictTypeEntity::getDictType, dictType); + if (StringUtils.isNotBlank(excludeId)) { + queryWrapper.ne(SysDictTypeEntity::getId, excludeId); + } + boolean exists = baseDao.exists(queryWrapper); + if (exists) { + throw new RenException("字典类型编码重复"); + } } - } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysParamsServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysParamsServiceImpl.java index b274f23c..02f595d2 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysParamsServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysParamsServiceImpl.java @@ -1,8 +1,10 @@ package xiaozhi.modules.sys.service.impl; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @@ -36,7 +38,7 @@ public class SysParamsServiceImpl extends BaseServiceImpl page(Map params) { IPage page = baseDao.selectPage( - getPage(params, Constant.CREATE_DATE, false), + getPage(params, null, false), getWrapper(params)); return getPageData(page, SysParamsDTO.class); @@ -54,7 +56,9 @@ public class SysParamsServiceImpl extends BaseServiceImpl wrapper = new QueryWrapper<>(); wrapper.eq("param_type", 1); - wrapper.like(StringUtils.isNotBlank(paramCode), "param_code", paramCode); + wrapper.nested(StringUtils.isNotBlank(paramCode), i -> i.like("param_code", paramCode) + .or() + .like("remark", paramCode)); return wrapper; } @@ -69,6 +73,8 @@ public class SysParamsServiceImpl extends BaseServiceImpl paramCodeList = baseDao.getParamCodeList(ids); String[] paramCodes = paramCodeList.toArray(new String[paramCodeList.size()]); - sysParamsRedis.delete(paramCodes); + if (paramCodes.length > 0) { + sysParamsRedis.delete(paramCodes); + } // 删除 deleteBatchIds(Arrays.asList(ids)); } @Override - public String getValue(String paramCode) { - String paramValue = sysParamsRedis.get(paramCode); - if (paramValue == null) { - paramValue = baseDao.getValueByCode(paramCode); + public String getValue(String paramCode, Boolean fromCache) { + String paramValue = null; + if (fromCache) { + paramValue = sysParamsRedis.get(paramCode); + if (paramValue == null) { + paramValue = baseDao.getValueByCode(paramCode); - sysParamsRedis.set(paramCode, paramValue); + sysParamsRedis.set(paramCode, paramValue); + } + } else { + paramValue = baseDao.getValueByCode(paramCode); } return paramValue; } @Override public T getValueObject(String paramCode, Class clazz) { - String paramValue = getValue(paramCode); + String paramValue = getValue(paramCode, true); if (StringUtils.isNotBlank(paramValue)) { return JsonUtils.parseObject(paramValue, clazz); } @@ -129,4 +198,50 @@ public class SysParamsServiceImpl extends BaseServiceImpl list = new ArrayList(); + list.add(Constant.SysMSMParam.SERVER_SMS_MAX_SEND_COUNT.getValue()); + list.add(Constant.SysMSMParam.ALIYUN_SMS_ACCESS_KEY_ID.getValue()); + list.add(Constant.SysMSMParam.ALIYUN_SMS_ACCESS_KEY_SECRET.getValue()); + list.add(Constant.SysMSMParam.ALIYUN_SMS_SIGN_NAME.getValue()); + list.add(Constant.SysMSMParam.ALIYUN_SMS_SMS_CODE_TEMPLATE_CODE.getValue()); + StringBuilder str = new StringBuilder(); + list.forEach(item -> { + if (!StringUtils.isNoneBlank(item)) { + str.append(",").append(item); + } + }); + if (!str.isEmpty()) { + String promptStr = "%s这些参数不可以为空"; + String substring = str.substring(1, str.length()); + throw new RenException(promptStr.formatted(substring)); + } + return true; + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java index e5b57159..9f35836f 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java @@ -30,6 +30,7 @@ import xiaozhi.modules.sys.dto.PasswordDTO; import xiaozhi.modules.sys.dto.SysUserDTO; import xiaozhi.modules.sys.entity.SysUserEntity; import xiaozhi.modules.sys.enums.SuperAdminEnum; +import xiaozhi.modules.sys.service.SysParamsService; import xiaozhi.modules.sys.service.SysUserService; import xiaozhi.modules.sys.vo.AdminPageUserVO; @@ -45,6 +46,8 @@ public class SysUserServiceImpl extends BaseServiceImpl queryWrapper = new QueryWrapper<>(); @@ -130,6 +133,10 @@ public class SysUserServiceImpl extends BaseServiceImpl page = baseDao.selectPage( getPage(params, "id", true), - new QueryWrapper().eq(StringUtils.isNotBlank(dto.getMobile()), "username", + new QueryWrapper().like(StringUtils.isNotBlank(dto.getMobile()), "username", dto.getMobile())); // 循环处理page获取回来的数据,返回需要的字段 List list = page.getRecords().stream().map(user -> { @@ -166,6 +173,7 @@ public class SysUserServiceImpl extends BaseServiceImpl(list, page.getTotal()); @@ -205,4 +213,17 @@ public class SysUserServiceImpl extends BaseServiceImpl()); + if (userCount == 0) { + return true; + } + return false; + } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserUtilServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserUtilServiceImpl.java index 8fd0351a..f4fe601a 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserUtilServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserUtilServiceImpl.java @@ -20,7 +20,9 @@ public class SysUserUtilServiceImpl extends BaseServiceImpl setter) { String userIdKey = RedisKeys.getUserIdKey(userId); - String username = redisUtils.get(userIdKey).toString(); + + Object value = redisUtils.get(userIdKey); + String username = (value != null) ? value.toString() : null; if(username != null){ setter.accept(username); }else { diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketClientManager.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketClientManager.java new file mode 100644 index 00000000..0c49aead --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketClientManager.java @@ -0,0 +1,315 @@ +package xiaozhi.modules.sys.utils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StopWatch; +import org.springframework.web.socket.*; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; +import org.springframework.web.socket.handler.AbstractWebSocketHandler; +import xiaozhi.common.utils.DateUtils; + +import java.io.Closeable; +import java.io.IOException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * WebSocketClientResource:支持 try-with-resources 模式 + */ +@Slf4j +public class WebSocketClientManager implements Closeable +{ + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + // 全局回调线程池 + private static final ExecutorService CALLBACK_EXECUTOR = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() { + private final AtomicInteger cnt = new AtomicInteger(); + + public Thread newThread(Runnable r) + { + Thread t = new Thread(r, "ws-callback-" + cnt.getAndIncrement()); + t.setDaemon(true); + return t; + } + }); + + private volatile WebSocketSession session; + private final BlockingQueue textMessageQueue; + private final BlockingQueue binaryMessageQueue; + private final CompletableFuture errorFuture; + private final long maxSessionDuration; + private final TimeUnit maxSessionDurationUnit; + + private volatile Consumer onText; + private volatile Consumer onBinary; + private volatile Consumer onError; + + private final String uri; + private final WebSocketHttpHeaders headers; + private final long connectTimeout; + private final TimeUnit connectUnit; + private final int queueCapacity; + + // 私有构造,仅由 Builder 调用 + private WebSocketClientManager(Builder b) { + this.uri = b.uri; + this.headers = b.headers != null ? b.headers : new WebSocketHttpHeaders(); + this.connectTimeout = b.connectTimeout; + this.connectUnit = b.connectUnit; + this.maxSessionDuration = b.maxSessionDuration; + this.maxSessionDurationUnit = b.maxSessionDurationUnit; + this.queueCapacity = b.queueCapacity; + this.textMessageQueue = new LinkedBlockingQueue<>(queueCapacity); + this.binaryMessageQueue = new LinkedBlockingQueue<>(queueCapacity); + this.errorFuture = new CompletableFuture<>(); + } + + public static WebSocketClientManager build(Builder b) throws InterruptedException, ExecutionException, TimeoutException, IOException { + WebSocketClientManager ws = new WebSocketClientManager(b); + StandardWebSocketClient client = new StandardWebSocketClient(); + CompletableFuture future = client.execute(ws.new InternalHandler(b.uri), b.headers, URI.create(b.uri)); + WebSocketSession sess = future.get(b.connectTimeout, b.connectUnit); + if (sess == null || !sess.isOpen()) + { + throw new IOException("握手失败或会话未打开"); + } + ws.session = sess; + return ws; + } + + /** + * 发送 Text + */ + public void sendText(String text) throws IOException { + session.sendMessage(new TextMessage(text)); + } + + public void sendBinary(byte[] data) throws IOException { + session.sendMessage(new BinaryMessage(data)); + } + + public void sendJson(Object payload) throws IOException { + String json = OBJECT_MAPPER.writeValueAsString(payload); + session.sendMessage(new TextMessage(json)); + } + + + + private List listenerCustom( + BlockingQueue queue, + Predicate predicate) + throws InterruptedException, TimeoutException, ExecutionException + { + List collected = new ArrayList<>(); + long deadline = System.currentTimeMillis() + maxSessionDurationUnit.toMillis(maxSessionDuration); + + while (true) { + if (errorFuture.isDone()) { + errorFuture.get(); + } + + long remaining = deadline - System.currentTimeMillis(); + if (remaining <= 0) { + throw new TimeoutException("等待批量消息超时"); + } + + T msg = queue.poll(remaining, TimeUnit.MILLISECONDS); + if (msg == null) { + throw new TimeoutException("等待批量消息超时"); + } + + collected.add(msg); + if (predicate.test(msg)) { + break; + } + } + close(); + return collected; + } + + /** + * 同步接收多条消息,直到 predicate 为 true 或超时抛异常; + * @return 返回监听期间的所有消息列表 + */ + public List listener(Predicate predicate) + throws InterruptedException, TimeoutException, ExecutionException + { + return listenerCustom(textMessageQueue, predicate); + } + + public List listenerBinary(Predicate predicate) + throws InterruptedException, TimeoutException, ExecutionException + { + return listenerCustom(binaryMessageQueue, predicate); + } + + /** + * 注册文本回调 + */ + public WebSocketClientManager onText(Consumer c) { + this.onText = c; + return this; + } + + /** + * 注册二进制回调 + */ + public WebSocketClientManager onBinary(Consumer c) { + this.onBinary = c; + return this; + } + + /** + * 注册错误回调 + */ + public WebSocketClientManager onError(Consumer c) { + this.onError = c; + return this; + } + + /** + * 关闭会话,try-with-resources / finally 自动调用 + */ + @Override + public void close() { + try { + if (session != null && session.isOpen()) { + session.close(CloseStatus.NORMAL); + } + } + catch (IOException ignored) {} + textMessageQueue.clear(); + binaryMessageQueue.clear(); + errorFuture.completeExceptionally(new IOException("WebSocket 已关闭")); + } + + private class InternalHandler extends AbstractWebSocketHandler { + private final String targetUri; + private final StopWatch stopWatch; + + InternalHandler(String targetUri) { + this.targetUri = targetUri; + this.stopWatch = new StopWatch(); + } + + /** + * 连接建立时回调 + */ + @Override + public void afterConnectionEstablished(WebSocketSession session) { + // 保存会话 + WebSocketClientManager.this.session = session; + this.stopWatch.start(); + log.info("ws连接成功, 目标URI: {}, 连接时间: {}", targetUri, DateUtils.getDateTimeNow(DateUtils.DATE_TIME_MILLIS_PATTERN)); + } + + /** + * 处理文本消息 + */ + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + String payload = message.getPayload(); + // 入队 + textMessageQueue.offer(payload); + // 回调用户注册的 onText + if (onText != null) { + CALLBACK_EXECUTOR.submit(() -> onText.accept(payload)); + } + } + + /** + * 处理二进制消息 + */ + @Override + protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception { + ByteBuffer buf = message.getPayload(); + byte[] data = new byte[buf.remaining()]; + buf.get(data); + // 入队 + binaryMessageQueue.offer(data); + // 回调用户注册的 onBinary + if (onBinary != null) { + CALLBACK_EXECUTOR.submit(() -> onBinary.accept(data)); + } + } + + /** + * 传输错误时回调 + */ + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { + super.handleTransportError(session, exception); + // 保持原有逻辑:完成 errorFuture、回调 onError、关闭会话、异步通知连接失败 + errorFuture.completeExceptionally(exception); + if (onError != null) { + CALLBACK_EXECUTOR.submit(() -> onError.accept(exception)); + } + session.close(CloseStatus.SERVER_ERROR); + } + + /** + * 连接关闭时回调 + */ + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { + super.afterConnectionClosed(session, status); + if (stopWatch.isRunning()) { + stopWatch.stop(); + } + log.info("ws连接关闭, 目标URI: {}, 关闭时间: {}, 连接总时长: {}s", + targetUri, DateUtils.getDateTimeNow(DateUtils.DATE_TIME_MILLIS_PATTERN), DateUtils.millsToSecond(stopWatch.getTotalTimeMillis())); + } + } + + public static class Builder { + private String uri; // 目标 WS URI + private long connectTimeout = 3; // 请求连接等待时间 + private TimeUnit connectUnit = TimeUnit.SECONDS; // 请求连接等待时间单位 + private long maxSessionDuration = 5; // 最大连线时间,默认5秒 + private TimeUnit maxSessionDurationUnit = TimeUnit.SECONDS; // 最大连线时间单位 + private int queueCapacity = 100; // 消息队列容量 + private WebSocketHttpHeaders headers; // 请求头 + + /** + * 目标 WS URI + */ + public Builder uri(String uri) { + this.uri = Objects.requireNonNull(uri); + return this; + } + + public Builder headers(WebSocketHttpHeaders h) { + this.headers = h; + return this; + } + + public Builder connectTimeout(long t, TimeUnit u) { + this.connectTimeout = t; + this.connectUnit = u; + return this; + } + + public Builder maxSessionDuration(long t, TimeUnit u) { + this.maxSessionDuration = t; + this.maxSessionDurationUnit = u; + return this; + } + + public Builder queueCapacity(int c) { + this.queueCapacity = c; + return this; + } + + public WebSocketClientManager build() throws InterruptedException, ExecutionException, TimeoutException, IOException { + return WebSocketClientManager.build(this); + } + + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketTestHandler.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketTestHandler.java new file mode 100644 index 00000000..d9563c29 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketTestHandler.java @@ -0,0 +1,46 @@ +package xiaozhi.modules.sys.utils; + +import java.util.concurrent.CompletableFuture; + +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketMessage; +import org.springframework.web.socket.WebSocketSession; + +public class WebSocketTestHandler implements WebSocketHandler { + private final CompletableFuture future; + + public WebSocketTestHandler(CompletableFuture future) { + this.future = future; + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + future.complete(true); + try { + session.close(); + } catch (Exception e) { + // 忽略关闭异常 + } + } + + @Override + public void handleMessage(WebSocketSession session, WebSocketMessage message) { + // 不需要处理消息 + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) { + future.complete(false); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) { + // 连接关闭时不做任何处理 + } + + @Override + public boolean supportsPartialMessages() { + return false; + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketValidator.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketValidator.java new file mode 100644 index 00000000..39bbe01f --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketValidator.java @@ -0,0 +1,60 @@ +package xiaozhi.modules.sys.utils; + +import java.net.URI; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.socket.WebSocketHttpHeaders; +import org.springframework.web.socket.client.WebSocketClient; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; + +public class WebSocketValidator { + private static final Logger logger = LoggerFactory.getLogger(WebSocketValidator.class); + + // WebSocket URL正则表达式 + private static final Pattern WS_URL_PATTERN = Pattern + .compile("^wss?://[\\w.-]+(?:\\.[\\w.-]+)*(?::\\d+)?(?:/[\\w.-]*)*$"); + + /** + * 验证WebSocket地址格式 + * + * @param url WebSocket地址 + * @return 是否有效 + */ + public static boolean validateUrlFormat(String url) { + if (StringUtils.isBlank(url)) { + return false; + } + return WS_URL_PATTERN.matcher(url.trim()).matches(); + } + + /** + * 测试WebSocket连接 + * + * @param url WebSocket地址 + * @return 是否可连接 + */ + public static boolean testConnection(String url) { + if (!validateUrlFormat(url)) { + return false; + } + + try { + WebSocketClient client = new StandardWebSocketClient(); + CompletableFuture future = new CompletableFuture<>(); + WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); + + client.execute(new WebSocketTestHandler(future), headers, URI.create(url)); + + // 等待最多5秒获取连接结果 + return future.get(5, TimeUnit.SECONDS); + } catch (Exception e) { + logger.error("WebSocket连接测试失败: {}", url, e); + return false; + } + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/vo/AdminPageUserVO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/vo/AdminPageUserVO.java index a406848f..9f832372 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/vo/AdminPageUserVO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/vo/AdminPageUserVO.java @@ -1,5 +1,7 @@ package xiaozhi.modules.sys.vo; +import java.util.Date; + import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -23,4 +25,7 @@ public class AdminPageUserVO { @Schema(description = "用户id") private String userid; + + @Schema(description = "注册时间") + private Date createDate; } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/vo/SysDictDataItem.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/vo/SysDictDataItem.java new file mode 100644 index 00000000..f6c81500 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/vo/SysDictDataItem.java @@ -0,0 +1,20 @@ +package xiaozhi.modules.sys.vo; + +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 字典数据VO + */ +@Data +@Schema(description = "字典数据项") +public class SysDictDataItem implements Serializable { + + @Schema(description = "字典标签") + private String name; + + @Schema(description = "字典值") + private String key; +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/vo/SysDictDataVO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/vo/SysDictDataVO.java new file mode 100644 index 00000000..40025b70 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/vo/SysDictDataVO.java @@ -0,0 +1,50 @@ +package xiaozhi.modules.sys.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 字典数据VO + */ +@Data +@Schema(description = "字典数据VO") +public class SysDictDataVO implements Serializable { + @Schema(description = "主键") + private Long id; + + @Schema(description = "字典类型ID") + private Long dictTypeId; + + @Schema(description = "字典标签") + private String dictLabel; + + @Schema(description = "字典值") + private String dictValue; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "排序") + private Integer sort; + + @Schema(description = "创建者") + private Long creator; + + @Schema(description = "创建者名称") + private String creatorName; + + @Schema(description = "创建时间") + private Date createDate; + + @Schema(description = "更新者") + private Long updater; + + @Schema(description = "更新者名称") + private String updaterName; + + @Schema(description = "更新时间") + private Date updateDate; +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/vo/SysDictTypeVO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/vo/SysDictTypeVO.java new file mode 100644 index 00000000..5e5844f3 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/vo/SysDictTypeVO.java @@ -0,0 +1,47 @@ +package xiaozhi.modules.sys.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 字典类型VO + */ +@Data +@Schema(description = "字典类型VO") +public class SysDictTypeVO implements Serializable { + @Schema(description = "主键") + private Long id; + + @Schema(description = "字典类型") + private String dictType; + + @Schema(description = "字典名称") + private String dictName; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "排序") + private Integer sort; + + @Schema(description = "创建者") + private Long creator; + + @Schema(description = "创建者名称") + private String creatorName; + + @Schema(description = "创建时间") + private Date createDate; + + @Schema(description = "更新者") + private Long updater; + + @Schema(description = "更新者名称") + private String updaterName; + + @Schema(description = "更新时间") + private Date updateDate; +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/controller/TimbreController.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/controller/TimbreController.java index 15ddf8fc..ff420c02 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/controller/TimbreController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/controller/TimbreController.java @@ -3,7 +3,6 @@ package xiaozhi.modules.timbre.controller; import java.util.Map; import org.apache.shiro.authz.annotation.RequiresPermissions; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -82,7 +81,7 @@ public class TimbreController { return new Result<>(); } - @DeleteMapping + @PostMapping("/delete") @Operation(summary = "音色删除") @RequiresPermissions("sys:role:superAdmin") public Result delete(@RequestBody String[] ids) { diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java index ed737db9..030e2b4a 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java @@ -24,7 +24,6 @@ public class TimbreDataDTO { private String name; @Schema(description = "备注") - @NotBlank(message = "{timbre.remark.require}") private String remark; @Schema(description = "排序") @@ -40,6 +39,5 @@ public class TimbreDataDTO { private String ttsVoice; @Schema(description = "音频播放地址") - @NotBlank(message = "{timbre.voiceDemo.require}") private String voiceDemo; } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java index f7ee176d..eebd55be 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java @@ -31,7 +31,7 @@ public interface TimbreService extends BaseService { * @param timbreId 音色表id * @return 音色信息 */ - TimbreDetailsVO get(Long timbreId); + TimbreDetailsVO get(String timbreId); /** * 保存音色信息 diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java index 54e5ec04..2ed5b1ab 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java @@ -47,7 +47,7 @@ public class TimbreServiceImpl extends BaseServiceImpl params.put(Constant.PAGE, dto.getPage()); params.put(Constant.LIMIT, dto.getLimit()); IPage page = baseDao.selectPage( - getPage(params, "sort", true), + getPage(params, null, true), // 定义查询条件 new QueryWrapper() // 必须按照ttsID查找 @@ -59,9 +59,33 @@ public class TimbreServiceImpl extends BaseServiceImpl } @Override - public TimbreDetailsVO get(Long timbreId) { + public TimbreDetailsVO get(String timbreId) { + if (StringUtils.isBlank(timbreId)) { + return null; + } + + // 先从Redis获取缓存 + String key = RedisKeys.getTimbreDetailsKey(timbreId); + TimbreDetailsVO cachedDetails = (TimbreDetailsVO) redisUtils.get(key); + if (cachedDetails != null) { + return cachedDetails; + } + + // 如果缓存中没有,则从数据库获取 TimbreEntity entity = baseDao.selectById(timbreId); - return ConvertUtils.sourceToTarget(entity, TimbreDetailsVO.class); + if (entity == null) { + return null; + } + + // 转换为VO对象 + TimbreDetailsVO details = ConvertUtils.sourceToTarget(entity, TimbreDetailsVO.class); + + // 存入Redis缓存 + if (details != null) { + redisUtils.set(key, details); + } + + return details; } @Override @@ -79,6 +103,8 @@ public class TimbreServiceImpl extends BaseServiceImpl TimbreEntity timbreEntity = ConvertUtils.sourceToTarget(dto, TimbreEntity.class); timbreEntity.setId(timbreId); baseDao.updateById(timbreEntity); + // 删除缓存 + redisUtils.delete(RedisKeys.getTimbreDetailsKey(timbreId)); } @Override diff --git a/main/manager-api/src/main/resources/application.yml b/main/manager-api/src/main/resources/application.yml index abccb0b5..3daf48a1 100644 --- a/main/manager-api/src/main/resources/application.yml +++ b/main/manager-api/src/main/resources/application.yml @@ -63,7 +63,4 @@ mybatis-plus: configuration-properties: prefix: blobType: BLOB - boolValue: TRUE - -app: - fronted-url: http://localhost:8001 \ No newline at end of file + boolValue: TRUE \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/202504061512.sql b/main/manager-api/src/main/resources/db/changelog/202504092335.sql similarity index 98% rename from main/manager-api/src/main/resources/db/changelog/202504061512.sql rename to main/manager-api/src/main/resources/db/changelog/202504092335.sql index e6956f85..9c254240 100644 --- a/main/manager-api/src/main/resources/db/changelog/202504061512.sql +++ b/main/manager-api/src/main/resources/db/changelog/202504092335.sql @@ -3,7 +3,7 @@ -- 初始化智能体模板数据 DELETE FROM `ai_agent_template`; INSERT INTO `ai_agent_template` VALUES ('9406648b5cc5fde1b8aa335b6f8b4f76', '小智', '湾湾小何', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定] -我是{{assistant_name}},来自中国台湾省的00后女生。讲话超级机车,"真的假的啦"这样的台湾腔,喜欢用"笑死""是在哈喽"等流行梗,但会偷偷研究男友的编程书籍。 +我是{{assistant_name}},来自中国台湾省的00后女生。讲话超级机车,"真的假的啦"这样的台湾腔,喜欢用"笑死"、"哈喽"等流行梗,但会偷偷研究男友的编程书籍。 [核心特征] - 讲话像连珠炮,但会突然冒出超温柔语气 - 用梗密度高 diff --git a/main/manager-api/src/main/resources/db/changelog/202504082210.sql b/main/manager-api/src/main/resources/db/changelog/202504112044.sql similarity index 54% rename from main/manager-api/src/main/resources/db/changelog/202504082210.sql rename to main/manager-api/src/main/resources/db/changelog/202504112044.sql index 65858527..9fd2257d 100644 --- a/main/manager-api/src/main/resources/db/changelog/202504082210.sql +++ b/main/manager-api/src/main/resources/db/changelog/202504112044.sql @@ -4,50 +4,52 @@ DELETE FROM `ai_model_config`; -- VAD模型配置 -INSERT INTO `ai_model_config` VALUES ('VAD_SileroVAD', 'VAD', 'SileroVAD', '语音活动检测', 1, 1, '{\"type\": \"silero\", \"model_dir\": \"models/snakers4_silero-vad\", \"threshold\": 0.5, \"min_silence_duration_ms\": 700}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('VAD_SileroVAD', 'VAD', 'SileroVAD', '语音活动检测', 1, 1, '{\"type\": \"silero\", \"model_dir\": \"models/snakers4_silero-vad\", \"threshold\": 0.5, \"min_silence_duration_ms\": 700}', NULL, NULL, 1, NULL, NULL, NULL, NULL); -- ASR模型配置 -INSERT INTO `ai_model_config` VALUES ('ASR_FunASR', 'ASR', 'FunASR', 'FunASR语音识别', 1, 1, '{\"type\": \"fun_local\", \"model_dir\": \"models/SenseVoiceSmall\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('ASR_SherpaASR', 'ASR', 'SherpaASR', 'Sherpa语音识别', 1, 0, '{\"type\": \"sherpa_onnx_local\", \"model_dir\": \"models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('ASR_DoubaoASR', 'ASR', 'DoubaoASR', '豆包语音识别', 1, 0, '{\"type\": \"doubao\", \"appid\": \"\", \"access_token\": \"\", \"cluster\": \"volcengine_input_common\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('ASR_FunASR', 'ASR', 'FunASR', 'FunASR语音识别', 1, 1, '{\"type\": \"fun_local\", \"model_dir\": \"models/SenseVoiceSmall\", \"output_dir\": \"tmp/\"}', NULL, NULL, 1, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('ASR_SherpaASR', 'ASR', 'SherpaASR', 'Sherpa语音识别', 0, 1, '{\"type\": \"sherpa_onnx_local\", \"model_dir\": \"models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17\", \"output_dir\": \"tmp/\"}', NULL, NULL, 2, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('ASR_DoubaoASR', 'ASR', 'DoubaoASR', '豆包语音识别', 0, 1, '{\"type\": \"doubao\", \"appid\": \"\", \"access_token\": \"\", \"cluster\": \"volcengine_input_common\", \"output_dir\": \"tmp/\"}', NULL, NULL, 3, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('ASR_TencentASR', 'ASR', 'TencentASR', '腾讯语音识别', 0, 1, '{\"type\": \"tencent\", \"appid\": \"\", \"secret_id\": \"\", \"secret_key\": \"你的secret_key\", \"output_dir\": \"tmp/\"}', NULL, NULL, 4, NULL, NULL, NULL, NULL); -- LLM模型配置 -INSERT INTO `ai_model_config` VALUES ('LLM_AliLLM', 'LLM', 'AliLLM', '通义千问', 1, 0, '{\"type\": \"openai\", \"base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\", \"model_name\": \"qwen-turbo\", \"api_key\": \"\", \"temperature\": 0.7, \"max_tokens\": 500, \"top_p\": 1, \"top_k\": 50, \"frequency_penalty\": 0}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('LLM_AliAppLLM', 'LLM', 'AliAppLLM', '通义百炼', 1, 0, '{\"type\": \"AliBL\", \"base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\", \"app_id\": \"\", \"api_key\": \"\", \"is_no_prompt\": true, \"ali_memory_id\": false}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('LLM_DoubaoLLM', 'LLM', 'DoubaoLLM', '豆包大模型', 1, 0, '{\"type\": \"openai\", \"base_url\": \"https://ark.cn-beijing.volces.com/api/v3\", \"model_name\": \"doubao-pro-32k-functioncall-241028\", \"api_key\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('LLM_DeepSeekLLM', 'LLM', 'DeepSeekLLM', 'DeepSeek', 1, 0, '{\"type\": \"openai\", \"model_name\": \"deepseek-chat\", \"url\": \"https://api.deepseek.com\", \"api_key\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('LLM_ChatGLMLLM', 'LLM', 'ChatGLMLLM', '智谱AI', 1, 1, '{\"type\": \"openai\", \"model_name\": \"glm-4-flash\", \"url\": \"https://open.bigmodel.cn/api/paas/v4/\", \"api_key\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('LLM_OllamaLLM', 'LLM', 'OllamaLLM', 'Ollama本地模型', 1, 0, '{\"type\": \"ollama\", \"model_name\": \"qwen2.5\", \"base_url\": \"http://localhost:11434\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('LLM_DifyLLM', 'LLM', 'DifyLLM', 'Dify', 1, 0, '{\"type\": \"dify\", \"base_url\": \"https://api.dify.ai/v1\", \"api_key\": \"\", \"mode\": \"chat-messages\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('LLM_GeminiLLM', 'LLM', 'GeminiLLM', '谷歌Gemini', 1, 0, '{\"type\": \"gemini\", \"api_key\": \"\", \"model_name\": \"gemini-2.0-flash\", \"http_proxy\": \"\", \"https_proxy\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('LLM_CozeLLM', 'LLM', 'CozeLLM', 'Coze', 1, 0, '{\"type\": \"coze\", \"bot_id\": \"\", \"user_id\": \"\", \"personal_access_token\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('LLM_LMStudioLLM', 'LLM', 'LMStudioLLM', 'LM Studio', 1, 0, '{\"type\": \"openai\", \"model_name\": \"deepseek-r1-distill-llama-8b@q4_k_m\", \"url\": \"http://localhost:1234/v1\", \"api_key\": \"lm-studio\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('LLM_FastgptLLM', 'LLM', 'FastgptLLM', 'FastGPT', 1, 0, '{\"type\": \"fastgpt\", \"base_url\": \"https://host/api/v1\", \"api_key\": \"fastgpt-xxx\", \"variables\": {\"k\": \"v\", \"k2\": \"v2\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('LLM_XinferenceLLM', 'LLM', 'XinferenceLLM', 'Xinference大模型', 1, 0, '{\"type\": \"xinference\", \"model_name\": \"qwen2.5:72b-AWQ\", \"base_url\": \"http://localhost:9997\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('LLM_XinferenceSmallLLM', 'LLM', 'XinferenceSmallLLM', 'Xinference小模型', 1, 0, '{\"type\": \"xinference\", \"model_name\": \"qwen2.5:3b-AWQ\", \"base_url\": \"http://localhost:9997\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('LLM_ChatGLMLLM', 'LLM', 'ChatGLMLLM', '智谱AI', 1, 1, '{\"type\": \"openai\", \"model_name\": \"glm-4-flash\", \"base_url\": \"https://open.bigmodel.cn/api/paas/v4/\", \"api_key\": \"你的api_key\"}', NULL, NULL, 1, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('LLM_OllamaLLM', 'LLM', 'OllamaLLM', 'Ollama本地模型', 0, 1, '{\"type\": \"ollama\", \"model_name\": \"qwen2.5\", \"base_url\": \"http://localhost:11434\"}', NULL, NULL, 2, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('LLM_AliLLM', 'LLM', 'AliLLM', '通义千问', 0, 1, '{\"type\": \"openai\", \"base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\", \"model_name\": \"qwen-turbo\", \"api_key\": \"你的api_key\", \"temperature\": 0.7, \"max_tokens\": 500, \"top_p\": 1, \"top_k\": 50, \"frequency_penalty\": 0}', NULL, NULL, 3, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('LLM_AliAppLLM', 'LLM', 'AliAppLLM', '通义百炼', 0, 1, '{\"type\": \"AliBL\", \"base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\", \"app_id\": \"你的app_id\", \"api_key\": \"你的api_key\", \"is_no_prompt\": true, \"ali_memory_id\": false}', NULL, NULL, 4, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('LLM_DoubaoLLM', 'LLM', 'DoubaoLLM', '豆包大模型', 0, 1, '{\"type\": \"openai\", \"base_url\": \"https://ark.cn-beijing.volces.com/api/v3\", \"model_name\": \"doubao-pro-32k-functioncall-241028\", \"api_key\": \"你的api_key\"}', NULL, NULL, 5, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('LLM_DeepSeekLLM', 'LLM', 'DeepSeekLLM', 'DeepSeek', 0, 1, '{\"type\": \"openai\", \"model_name\": \"deepseek-chat\", \"base_url\": \"https://api.deepseek.com\", \"api_key\": \"你的api_key\"}', NULL, NULL, 6, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('LLM_DifyLLM', 'LLM', 'DifyLLM', 'Dify', 0, 1, '{\"type\": \"dify\", \"base_url\": \"https://api.dify.ai/v1\", \"api_key\": \"你的api_key\", \"mode\": \"chat-messages\"}', NULL, NULL, 7, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('LLM_GeminiLLM', 'LLM', 'GeminiLLM', '谷歌Gemini', 0, 1, '{\"type\": \"gemini\", \"api_key\": \"你的api_key\", \"model_name\": \"gemini-2.0-flash\", \"http_proxy\": \"\", \"https_proxy\": \"\"}', NULL, NULL, 8, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('LLM_CozeLLM', 'LLM', 'CozeLLM', 'Coze', 0, 1, '{\"type\": \"coze\", \"bot_id\": \"你的bot_id\", \"user_id\": \"你的user_id\", \"personal_access_token\": \"你的personal_access_token\"}', NULL, NULL, 9, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('LLM_LMStudioLLM', 'LLM', 'LMStudioLLM', 'LM Studio', 0, 1, '{\"type\": \"openai\", \"model_name\": \"deepseek-r1-distill-llama-8b@q4_k_m\", \"base_url\": \"http://localhost:1234/v1\", \"api_key\": \"lm-studio\"}', NULL, NULL, 10, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('LLM_FastgptLLM', 'LLM', 'FastgptLLM', 'FastGPT', 0, 1, '{\"type\": \"fastgpt\", \"base_url\": \"https://host/api/v1\", \"api_key\": \"fastgpt-xxx\", \"variables\": {\"k\": \"v\", \"k2\": \"v2\"}}', NULL, NULL, 11, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('LLM_XinferenceLLM', 'LLM', 'XinferenceLLM', 'Xinference大模型', 0, 1, '{\"type\": \"xinference\", \"model_name\": \"qwen2.5:72b-AWQ\", \"base_url\": \"http://localhost:9997\"}', NULL, NULL, 12, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('LLM_XinferenceSmallLLM', 'LLM', 'XinferenceSmallLLM', 'Xinference小模型', 0, 1, '{\"type\": \"xinference\", \"model_name\": \"qwen2.5:3b-AWQ\", \"base_url\": \"http://localhost:9997\"}', NULL, NULL, 13, NULL, NULL, NULL, NULL); -- TTS模型配置 -INSERT INTO `ai_model_config` VALUES ('TTS_EdgeTTS', 'TTS', 'EdgeTTS', 'Edge语音合成', 1, 1, '{\"type\": \"edge\", \"voice\": \"zh-CN-XiaoxiaoNeural\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('TTS_DoubaoTTS', 'TTS', 'DoubaoTTS', '豆包语音合成', 1, 0, '{\"type\": \"doubao\", \"api_url\": \"https://openspeech.bytedance.com/api/v1/tts\", \"voice\": \"BV001_streaming\", \"output_dir\": \"tmp/\", \"authorization\": \"Bearer;\", \"appid\": \"\", \"access_token\": \"\", \"cluster\": \"volcano_tts\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('TTS_CosyVoiceSiliconflow', 'TTS', 'CosyVoiceSiliconflow', '硅基流动语音合成', 1, 0, '{\"type\": \"siliconflow\", \"model\": \"FunAudioLLM/CosyVoice2-0.5B\", \"voice\": \"FunAudioLLM/CosyVoice2-0.5B:alex\", \"output_dir\": \"tmp/\", \"access_token\": \"\", \"response_format\": \"wav\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('TTS_CozeCnTTS', 'TTS', 'CozeCnTTS', 'Coze中文语音合成', 1, 0, '{\"type\": \"cozecn\", \"voice\": \"7426720361733046281\", \"output_dir\": \"tmp/\", \"access_token\": \"\", \"response_format\": \"wav\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('TTS_FishSpeech', 'TTS', 'FishSpeech', 'FishSpeech语音合成', 1, 0, '{\"type\": \"fishspeech\", \"output_dir\": \"tmp/\", \"response_format\": \"wav\", \"reference_id\": null, \"reference_audio\": [\"/tmp/test.wav\"], \"reference_text\": [\"你弄来这些吟词宴曲来看,还是这些混话来欺负我。\"], \"normalize\": true, \"max_new_tokens\": 1024, \"chunk_length\": 200, \"top_p\": 0.7, \"repetition_penalty\": 1.2, \"temperature\": 0.7, \"streaming\": false, \"use_memory_cache\": \"on\", \"seed\": null, \"channels\": 1, \"rate\": 44100, \"api_key\": \"\", \"api_url\": \"http://127.0.0.1:8080/v1/tts\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('TTS_GPT_SOVITS_V2', 'TTS', 'GPT_SOVITS_V2', 'GPT-SoVITS V2', 1, 0, '{\"type\": \"gpt_sovits_v2\", \"url\": \"http://127.0.0.1:9880/tts\", \"output_dir\": \"tmp/\", \"text_lang\": \"auto\", \"ref_audio_path\": \"caixukun.wav\", \"prompt_text\": \"\", \"prompt_lang\": \"zh\", \"top_k\": 5, \"top_p\": 1, \"temperature\": 1, \"text_split_method\": \"cut0\", \"batch_size\": 1, \"batch_threshold\": 0.75, \"split_bucket\": true, \"return_fragment\": false, \"speed_factor\": 1.0, \"streaming_mode\": false, \"seed\": -1, \"parallel_infer\": true, \"repetition_penalty\": 1.35, \"aux_ref_audio_paths\": []}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('TTS_GPT_SOVITS_V3', 'TTS', 'GPT_SOVITS_V3', 'GPT-SoVITS V3', 1, 0, '{\"type\": \"gpt_sovits_v3\", \"url\": \"http://127.0.0.1:9880\", \"output_dir\": \"tmp/\", \"text_language\": \"auto\", \"refer_wav_path\": \"caixukun.wav\", \"prompt_language\": \"zh\", \"prompt_text\": \"\", \"top_k\": 15, \"top_p\": 1.0, \"temperature\": 1.0, \"cut_punc\": \"\", \"speed\": 1.0, \"inp_refs\": [], \"sample_steps\": 32, \"if_sr\": false}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('TTS_MinimaxTTS', 'TTS', 'MinimaxTTS', 'MiniMax语音合成', 1, 0, '{\"type\": \"minimax\", \"output_dir\": \"tmp/\", \"group_id\": \"\", \"api_key\": \"\", \"model\": \"speech-01-turbo\", \"voice_id\": \"female-shaonv\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('TTS_AliyunTTS', 'TTS', 'AliyunTTS', '阿里云语音合成', 1, 0, '{\"type\": \"aliyun\", \"output_dir\": \"tmp/\", \"appkey\": \"\", \"token\": \"\", \"voice\": \"xiaoyun\", \"access_key_id\": \"\", \"access_key_secret\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('TTS_TTS302AI', 'TTS', 'TTS302AI', '302AI语音合成', 1, 0, '{\"type\": \"doubao\", \"api_url\": \"https://api.302ai.cn/doubao/tts_hd\", \"authorization\": \"Bearer \", \"voice\": \"zh_female_wanwanxiaohe_moon_bigtts\", \"output_dir\": \"tmp/\", \"access_token\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('TTS_GizwitsTTS', 'TTS', 'GizwitsTTS', '机智云语音合成', 1, 0, '{\"type\": \"doubao\", \"api_url\": \"https://bytedance.gizwitsapi.com/api/v1/tts\", \"authorization\": \"Bearer \", \"voice\": \"zh_female_wanwanxiaohe_moon_bigtts\", \"output_dir\": \"tmp/\", \"access_token\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('TTS_ACGNTTS', 'TTS', 'ACGNTTS', 'ACGN语音合成', 1, 0, '{\"type\": \"ttson\", \"token\": \"\", \"voice_id\": 1695, \"speed_factor\": 1, \"pitch_factor\": 0, \"volume_change_dB\": 0, \"to_lang\": \"ZH\", \"url\": \"https://u95167-bd74-2aef8085.westx.seetacloud.com:8443/flashsummary/tts?token=\", \"format\": \"mp3\", \"output_dir\": \"tmp/\", \"emotion\": 1}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('TTS_OpenAITTS', 'TTS', 'OpenAITTS', 'OpenAI语音合成', 1, 0, '{\"type\": \"openai\", \"api_key\": \"\", \"api_url\": \"https://api.openai.com/v1/audio/speech\", \"model\": \"tts-1\", \"voice\": \"onyx\", \"speed\": 1, \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('TTS_CustomTTS', 'TTS', 'CustomTTS', '自定义语音合成', 1, 0, '{\"type\": \"custom\", \"url\": \"http://127.0.0.1:9880/tts\", \"params\": {}, \"headers\": {}, \"format\": \"wav\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_EdgeTTS', 'TTS', 'EdgeTTS', 'Edge语音合成', 1, 1, '{\"type\": \"edge\", \"voice\": \"zh-CN-XiaoxiaoNeural\", \"output_dir\": \"tmp/\"}', NULL, NULL, 1, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_DoubaoTTS', 'TTS', 'DoubaoTTS', '豆包语音合成', 0, 1, '{\"type\": \"doubao\", \"api_url\": \"https://openspeech.bytedance.com/api/v1/tts\", \"voice\": \"BV001_streaming\", \"output_dir\": \"tmp/\", \"authorization\": \"Bearer;\", \"appid\": \"\", \"access_token\": \"\", \"cluster\": \"volcano_tts\"}', NULL, NULL, 2, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_CosyVoiceSiliconflow', 'TTS', 'CosyVoiceSiliconflow', '硅基流动语音合成', 0, 1, '{\"type\": \"siliconflow\", \"model\": \"FunAudioLLM/CosyVoice2-0.5B\", \"voice\": \"FunAudioLLM/CosyVoice2-0.5B:alex\", \"output_dir\": \"tmp/\", \"access_token\": \"\", \"response_format\": \"wav\"}', NULL, NULL, 3, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_CozeCnTTS', 'TTS', 'CozeCnTTS', 'Coze中文语音合成', 0, 1, '{\"type\": \"cozecn\", \"voice\": \"7426720361733046281\", \"output_dir\": \"tmp/\", \"access_token\": \"\", \"response_format\": \"wav\"}', NULL, NULL, 4, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_FishSpeech', 'TTS', 'FishSpeech', 'FishSpeech语音合成', 0, 1, '{\"type\": \"fishspeech\", \"output_dir\": \"tmp/\", \"response_format\": \"wav\", \"reference_id\": null, \"reference_audio\": [\"/tmp/test.wav\"], \"reference_text\": [\"你弄来这些吟词宴曲来看,还是这些混话来欺负我。\"], \"normalize\": true, \"max_new_tokens\": 1024, \"chunk_length\": 200, \"top_p\": 0.7, \"repetition_penalty\": 1.2, \"temperature\": 0.7, \"streaming\": false, \"use_memory_cache\": \"on\", \"seed\": null, \"channels\": 1, \"rate\": 44100, \"api_key\": \"\", \"api_url\": \"http://127.0.0.1:8080/v1/tts\"}', NULL, NULL, 5, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_GPT_SOVITS_V2', 'TTS', 'GPT_SOVITS_V2', 'GPT-SoVITS V2', 0, 1, '{\"type\": \"gpt_sovits_v2\", \"url\": \"http://127.0.0.1:9880/tts\", \"output_dir\": \"tmp/\", \"text_lang\": \"auto\", \"ref_audio_path\": \"caixukun.wav\", \"prompt_text\": \"\", \"prompt_lang\": \"zh\", \"top_k\": 5, \"top_p\": 1, \"temperature\": 1, \"text_split_method\": \"cut0\", \"batch_size\": 1, \"batch_threshold\": 0.75, \"split_bucket\": true, \"return_fragment\": false, \"speed_factor\": 1.0, \"streaming_mode\": false, \"seed\": -1, \"parallel_infer\": true, \"repetition_penalty\": 1.35, \"aux_ref_audio_paths\": []}', NULL, NULL, 6, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_GPT_SOVITS_V3', 'TTS', 'GPT_SOVITS_V3', 'GPT-SoVITS V3', 0, 1, '{\"type\": \"gpt_sovits_v3\", \"url\": \"http://127.0.0.1:9880\", \"output_dir\": \"tmp/\", \"text_language\": \"auto\", \"refer_wav_path\": \"caixukun.wav\", \"prompt_language\": \"zh\", \"prompt_text\": \"\", \"top_k\": 15, \"top_p\": 1.0, \"temperature\": 1.0, \"cut_punc\": \"\", \"speed\": 1.0, \"inp_refs\": [], \"sample_steps\": 32, \"if_sr\": false}', NULL, NULL, 7, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_MinimaxTTS', 'TTS', 'MinimaxTTS', 'MiniMax语音合成', 0, 1, '{\"type\": \"minimax\", \"output_dir\": \"tmp/\", \"group_id\": \"\", \"api_key\": \"你的api_key\", \"model\": \"speech-01-turbo\", \"voice_id\": \"female-shaonv\"}', NULL, NULL, 8, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_AliyunTTS', 'TTS', 'AliyunTTS', '阿里云语音合成', 0, 1, '{\"type\": \"aliyun\", \"output_dir\": \"tmp/\", \"appkey\": \"\", \"token\": \"\", \"voice\": \"xiaoyun\", \"access_key_id\": \"\", \"access_key_secret\": \"\"}', NULL, NULL, 9, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_TTS302AI', 'TTS', 'TTS302AI', '302AI语音合成', 0, 1, '{\"type\": \"doubao\", \"api_url\": \"https://api.302ai.cn/doubao/tts_hd\", \"authorization\": \"Bearer \", \"voice\": \"zh_female_wanwanxiaohe_moon_bigtts\", \"output_dir\": \"tmp/\", \"access_token\": \"\"}', NULL, NULL, 10, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_GizwitsTTS', 'TTS', 'GizwitsTTS', '机智云语音合成', 0, 1, '{\"type\": \"doubao\", \"api_url\": \"https://bytedance.gizwitsapi.com/api/v1/tts\", \"authorization\": \"Bearer \", \"voice\": \"zh_female_wanwanxiaohe_moon_bigtts\", \"output_dir\": \"tmp/\", \"access_token\": \"\"}', NULL, NULL, 11, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_ACGNTTS', 'TTS', 'ACGNTTS', 'ACGN语音合成', 0, 1, '{\"type\": \"ttson\", \"token\": \"\", \"voice_id\": 1695, \"speed_factor\": 1, \"pitch_factor\": 0, \"volume_change_dB\": 0, \"to_lang\": \"ZH\", \"url\": \"https://u95167-bd74-2aef8085.westx.seetacloud.com:8443/flashsummary/tts?token=\", \"format\": \"mp3\", \"output_dir\": \"tmp/\", \"emotion\": 1}', NULL, NULL, 12, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_OpenAITTS', 'TTS', 'OpenAITTS', 'OpenAI语音合成', 0, 1, '{\"type\": \"openai\", \"api_key\": \"你的api_key\", \"api_url\": \"https://api.openai.com/v1/audio/speech\", \"model\": \"tts-1\", \"voice\": \"onyx\", \"speed\": 1, \"output_dir\": \"tmp/\"}', NULL, NULL, 13, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_CustomTTS', 'TTS', 'CustomTTS', '自定义语音合成', 0, 1, '{\"type\": \"custom\", \"url\": \"http://127.0.0.1:9880/tts\", \"params\": {}, \"headers\": {}, \"format\": \"wav\", \"output_dir\": \"tmp/\"}', NULL, NULL, 14, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_TencentTTS', 'TTS', 'TencentTTS', '腾讯语音合成', 0, 1, '{\"type\": \"tencent\", \"appid\": \"\", \"secret_id\": \"\", \"secret_key\": \"\", \"region\": \"ap-guangzhou\", \"voice\": \"101001\", \"output_dir\": \"tmp/\"}', NULL, NULL, 3, NULL, NULL, NULL, NULL); -- Memory模型配置 -INSERT INTO `ai_model_config` VALUES ('Memory_mem0ai', 'Memory', 'mem0ai', 'Mem0AI记忆', 1, 0, '{\"type\": \"mem0ai\", \"api_key\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('Memory_nomem', 'Memory', 'nomem', '无记忆', 1, 1, '{\"type\": \"nomem\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('Memory_mem_local_short', 'Memory', 'mem_local_short', '本地短期记忆', 1, 0, '{\"type\": \"mem_local_short\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('Memory_nomem', 'Memory', 'nomem', '无记忆', 1, 1, '{\"type\": \"nomem\"}', NULL, NULL, 1, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('Memory_mem_local_short', 'Memory', 'mem_local_short', '本地短期记忆', 0, 1, '{\"type\": \"mem_local_short\"}', NULL, NULL, 2, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('Memory_mem0ai', 'Memory', 'mem0ai', 'Mem0AI记忆', 0, 1, '{\"type\": \"mem0ai\", \"api_key\": \"你的api_key\"}', NULL, NULL, 3, NULL, NULL, NULL, NULL); -- Intent模型配置 -INSERT INTO `ai_model_config` VALUES ('Intent_nointent', 'Intent', 'nointent', '无意图识别', 1, 0, '{\"type\": \"nointent\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('Intent_intent_llm', 'Intent', 'intent_llm', 'LLM意图识别', 1, 0, '{\"type\": \"intent_llm\", \"llm\": \"ChatGLMLLM\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('Intent_function_call', 'Intent', 'function_call', '函数调用意图识别', 1, 1, '{\"type\": \"function_call\", \"functions\": [\"change_role\", \"get_weather\", \"get_news\", \"play_music\"]}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('Intent_nointent', 'Intent', 'nointent', '无意图识别', 1, 0, '{\"type\": \"nointent\"}', NULL, NULL, 1, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('Intent_intent_llm', 'Intent', 'intent_llm', 'LLM意图识别', 0, 1, '{\"type\": \"intent_llm\", \"llm\": \"ChatGLMLLM\"}', NULL, NULL, 2, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('Intent_function_call', 'Intent', 'function_call', '函数调用意图识别', 0, 1, '{\"type\": \"function_call\", \"functions\": [\"change_role\", \"get_weather\", \"get_news\", \"play_music\"]}', NULL, NULL, 3, NULL, NULL, NULL, NULL); diff --git a/main/manager-api/src/main/resources/db/changelog/202504112058.sql b/main/manager-api/src/main/resources/db/changelog/202504112058.sql new file mode 100644 index 00000000..e19f2da1 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202504112058.sql @@ -0,0 +1,61 @@ +-- 本文件用于初始化系统参数数据,无需手动执行,在项目启动时会自动执行 +-- -------------------------------------------------------- +-- 初始化参数管理配置 +DROP TABLE IF EXISTS sys_params; +-- 参数管理 +create table sys_params +( + id bigint NOT NULL COMMENT 'id', + param_code varchar(100) COMMENT '参数编码', + param_value varchar(2000) COMMENT '参数值', + value_type varchar(20) default 'string' COMMENT '值类型:string-字符串,number-数字,boolean-布尔,array-数组', + param_type tinyint unsigned default 1 COMMENT '类型 0:系统参数 1:非系统参数', + remark varchar(200) COMMENT '备注', + creator bigint COMMENT '创建者', + create_date datetime COMMENT '创建时间', + updater bigint COMMENT '更新者', + update_date datetime COMMENT '更新时间', + primary key (id), + unique key uk_param_code (param_code) +)ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COMMENT='参数管理'; + +-- 服务器配置 +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (100, 'server.ip', '0.0.0.0', 'string', 1, '服务器监听IP地址'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (101, 'server.port', '8000', 'number', 1, '服务器监听端口'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (102, 'server.secret', 'null', 'string', 1, '服务器密钥'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (201, 'log.log_format', '{time:YYMMDD HH:mm:ss}[{version}-{selected_module}][{extra[tag]}]-{level}-{message}', 'string', 1, '控制台日志格式'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (202, 'log.log_format_file', '{time:YYYY-MM-DD HH:mm:ss} - {version}_{selected_module} - {name} - {level} - {extra[tag]} - {message}', 'string', 1, '文件日志格式'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (203, 'log.log_level', 'INFO', 'string', 1, '日志级别'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (204, 'log.log_dir', 'tmp', 'string', 1, '日志目录'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (205, 'log.log_file', 'server.log', 'string', 1, '日志文件名'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (206, 'log.data_dir', 'data', 'string', 1, '数据目录'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (301, 'delete_audio', 'true', 'boolean', 1, '是否删除使用后的音频文件'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (302, 'close_connection_no_voice_time', '120', 'number', 1, '无语音输入断开连接时间(秒)'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (303, 'tts_timeout', '10', 'number', 1, 'TTS请求超时时间(秒)'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (304, 'enable_wakeup_words_response_cache', 'false', 'boolean', 1, '是否开启唤醒词加速'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (305, 'enable_greeting', 'true', 'boolean', 1, '是否开启开场回复'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (306, 'enable_stop_tts_notify', 'false', 'boolean', 1, '是否开启结束提示音'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (307, 'stop_tts_notify_voice', 'config/assets/tts_notify.mp3', 'string', 1, '结束提示音文件路径'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (308, 'exit_commands', '退出;关闭', 'array', 1, '退出命令列表'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (309, 'xiaozhi', '{ + "type": "hello", + "version": 1, + "transport": "websocket", + "audio_params": { + "format": "opus", + "sample_rate": 16000, + "channels": 1, + "frame_duration": 60 + } +}', 'json', 1, '小智类型'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (310, 'wakeup_words', '你好小智;你好小志;小爱同学;你好小鑫;你好小新;小美同学;小龙小龙;喵喵同学;小滨小滨;小冰小冰', 'array', 1, '唤醒词列表,用于识别唤醒词'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (400, 'plugins.get_weather.api_key', 'a861d0d5e7bf4ee1a83d9a9e4f96d4da', 'string', 1, '天气插件API密钥'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (401, 'plugins.get_weather.default_location', '广州', 'string', 1, '天气插件默认城市'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (410, 'plugins.get_news.default_rss_url', 'https://www.chinanews.com.cn/rss/society.xml', 'string', 1, '新闻插件默认RSS地址'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (411, 'plugins.get_news.category_urls', '{"society":"https://www.chinanews.com.cn/rss/society.xml","world":"https://www.chinanews.com.cn/rss/world.xml","finance":"https://www.chinanews.com.cn/rss/finance.xml"}', 'json', 1, '新闻插件分类RSS地址'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (421, 'plugins.home_assistant.devices', '客厅,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1;卧室,台灯,switch.iot_cn_831898993_socn1_on_p_2_1', 'array', 1, 'Home Assistant设备列表'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (422, 'plugins.home_assistant.base_url', 'http://homeassistant.local:8123', 'string', 1, 'Home Assistant服务器地址'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (423, 'plugins.home_assistant.api_key', '你的home assistant api访问令牌', 'string', 1, 'Home Assistant API密钥'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (430, 'plugins.play_music.music_dir', './music', 'string', 1, '音乐文件存放路径'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (431, 'plugins.play_music.music_ext', 'mp3;wav;p3', 'array', 1, '音乐文件类型'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (432, 'plugins.play_music.refresh_time', '300', 'number', 1, '音乐列表刷新间隔(秒)'); diff --git a/main/manager-api/src/main/resources/db/changelog/202504151206.sql b/main/manager-api/src/main/resources/db/changelog/202504151206.sql new file mode 100644 index 00000000..cb51cef9 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202504151206.sql @@ -0,0 +1,28 @@ +-- 对0.3.0版本之前的参数进行修改 +update `sys_params` set param_value = '.mp3;.wav;.p3' where param_code = 'plugins.play_music.music_ext'; +update `ai_model_config` set config_json = '{\"type\": \"intent_llm\", \"llm\": \"LLM_ChatGLMLLM\"}' where id = 'Intent_intent_llm'; + +-- 添加edge音色 +delete from `ai_tts_voice` where tts_model_id = 'TTS_EdgeTTS'; +INSERT INTO `ai_tts_voice` VALUES +('TTS_EdgeTTS0001', 'TTS_EdgeTTS', 'EdgeTTS女声-晓晓', 'zh-CN-XiaoxiaoNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0002', 'TTS_EdgeTTS', 'EdgeTTS男声-云扬', 'zh-CN-YunyangNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0003', 'TTS_EdgeTTS', 'EdgeTTS女声-晓伊', 'zh-CN-XiaoyiNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0004', 'TTS_EdgeTTS', 'EdgeTTS男声-云健', 'zh-CN-YunjianNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0005', 'TTS_EdgeTTS', 'EdgeTTS男声-云希', 'zh-CN-YunxiNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0006', 'TTS_EdgeTTS', 'EdgeTTS男声-云夏', 'zh-CN-YunxiaNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0007', 'TTS_EdgeTTS', 'EdgeTTS女声-辽宁小贝', 'zh-CN-liaoning-XiaobeiNeural', '辽宁', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0008', 'TTS_EdgeTTS', 'EdgeTTS女声-陕西小妮', 'zh-CN-shaanxi-XiaoniNeural', '陕西', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0009', 'TTS_EdgeTTS', 'EdgeTTS女声-香港海佳', 'zh-HK-HiuGaaiNeural', '粤语', 'General', 'Friendly, Positive', 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0010', 'TTS_EdgeTTS', 'EdgeTTS女声-香港海曼', 'zh-HK-HiuMaanNeural', '粤语', 'General', 'Friendly, Positive', 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0011', 'TTS_EdgeTTS', 'EdgeTTS男声-香港万龙', 'zh-HK-WanLungNeural', '粤语', 'General', 'Friendly, Positive', 1, NULL, NULL, NULL, NULL); + +-- 增加是否允许用户注册参数 +delete from `sys_params` where id in (103,104); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (103, 'server.allow_user_register', 'false', 'boolean', 1, '是否运行管理员以外的人注册'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (104, 'server.fronted_url', 'http://xiaozhi.server.com', 'string', 1, '下发六位验证码时显示的控制面板地址'); + +-- 修正CosyVoiceSiliconflow音色 +delete from `ai_tts_voice` where tts_model_id = 'TTS_CosyVoiceSiliconflow'; +INSERT INTO `ai_tts_voice` VALUES ('TTS_CosyVoiceSiliconflow0001', 'TTS_CosyVoiceSiliconflow', 'CosyVoice男声', 'FunAudioLLM/CosyVoice2-0.5B:alex', '中文', 'https://example.com/cosyvoice/alex.mp3', NULL, 6, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_CosyVoiceSiliconflow0002', 'TTS_CosyVoiceSiliconflow', 'CosyVoice女声', 'FunAudioLLM/CosyVoice2-0.5B:bella', '中文', 'https://example.com/cosyvoice/bella.mp3', NULL, 6, NULL, NULL, NULL, NULL); diff --git a/main/manager-api/src/main/resources/db/changelog/202504181536.sql b/main/manager-api/src/main/resources/db/changelog/202504181536.sql new file mode 100644 index 00000000..327aa605 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202504181536.sql @@ -0,0 +1,7 @@ +-- 调整意图识别配置 +delete from `ai_model_config` where id = 'Intent_function_call'; +INSERT INTO `ai_model_config` VALUES ('Intent_function_call', 'Intent', 'function_call', '函数调用意图识别', 0, 1, '{\"type\": \"function_call\", \"functions\": \"change_role;get_weather;get_news;play_music\"}', NULL, NULL, 3, NULL, NULL, NULL, NULL); + +-- 增加单台设备每天最多聊天句数 +delete from `sys_params` where id = 105; +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (105, 'device_max_output_size', '0', 'number', 1, '单台设备每天最多输出字数,0表示不限制'); \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/202504221135.sql b/main/manager-api/src/main/resources/db/changelog/202504221135.sql new file mode 100644 index 00000000..79c80446 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202504221135.sql @@ -0,0 +1,88 @@ +-- 删除无用模型供应器 +delete from `ai_model_provider` where id = 'SYSTEM_LLM_doubao'; +delete from `ai_model_provider` where id = 'SYSTEM_LLM_chatglm'; +delete from `ai_model_provider` where id = 'SYSTEM_TTS_302ai'; +delete from `ai_model_provider` where id = 'SYSTEM_TTS_gizwits'; + +-- 添加模型供应器 +delete from `ai_model_provider` where id = 'SYSTEM_ASR_TencentASR'; +INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES +('SYSTEM_ASR_TencentASR', 'ASR', 'tencent', '腾讯语音识别', '[{"key":"appid","label":"应用ID","type":"string"},{"key":"secret_id","label":"Secret ID","type":"string"},{"key":"secret_key","label":"Secret Key","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 4, 1, NOW(), 1, NOW()); + +-- 添加腾讯语音合成模型供应器 +delete from `ai_model_provider` where id = 'SYSTEM_TTS_TencentTTS'; +INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES +('SYSTEM_TTS_TencentTTS', 'TTS', 'tencent', '腾讯语音合成', '[{"key":"appid","label":"应用ID","type":"string"},{"key":"secret_id","label":"Secret ID","type":"string"},{"key":"secret_key","label":"Secret Key","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"},{"key":"region","label":"区域","type":"string"},{"key":"voice","label":"音色ID","type":"string"}]', 5, 1, NOW(), 1, NOW()); + + +-- 添加edge音色 +delete from `ai_tts_voice` where id in ('TTS_EdgeTTS0001', 'TTS_EdgeTTS0002', 'TTS_EdgeTTS0003', 'TTS_EdgeTTS0004', 'TTS_EdgeTTS0005', 'TTS_EdgeTTS0006', 'TTS_EdgeTTS0007', 'TTS_EdgeTTS0008', 'TTS_EdgeTTS0009', 'TTS_EdgeTTS0010', 'TTS_EdgeTTS0011'); +INSERT INTO `ai_tts_voice` VALUES +('TTS_EdgeTTS0001', 'TTS_EdgeTTS', 'EdgeTTS女声-晓晓', 'zh-CN-XiaoxiaoNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0002', 'TTS_EdgeTTS', 'EdgeTTS男声-云扬', 'zh-CN-YunyangNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0003', 'TTS_EdgeTTS', 'EdgeTTS女声-晓伊', 'zh-CN-XiaoyiNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0004', 'TTS_EdgeTTS', 'EdgeTTS男声-云健', 'zh-CN-YunjianNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0005', 'TTS_EdgeTTS', 'EdgeTTS男声-云希', 'zh-CN-YunxiNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0006', 'TTS_EdgeTTS', 'EdgeTTS男声-云夏', 'zh-CN-YunxiaNeural', '普通话', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0007', 'TTS_EdgeTTS', 'EdgeTTS女声-辽宁小贝', 'zh-CN-liaoning-XiaobeiNeural', '辽宁', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0008', 'TTS_EdgeTTS', 'EdgeTTS女声-陕西小妮', 'zh-CN-shaanxi-XiaoniNeural', '陕西', NULL, NULL, 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0009', 'TTS_EdgeTTS', 'EdgeTTS女声-香港海佳', 'zh-HK-HiuGaaiNeural', '粤语', 'General', 'Friendly, Positive', 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0010', 'TTS_EdgeTTS', 'EdgeTTS女声-香港海曼', 'zh-HK-HiuMaanNeural', '粤语', 'General', 'Friendly, Positive', 1, NULL, NULL, NULL, NULL), +('TTS_EdgeTTS0011', 'TTS_EdgeTTS', 'EdgeTTS男声-香港万龙', 'zh-HK-WanLungNeural', '粤语', 'General', 'Friendly, Positive', 1, NULL, NULL, NULL, NULL); + +-- DoubaoTTS音色 +delete from `ai_tts_voice` where id in ('TTS_DoubaoTTS0001', 'TTS_DoubaoTTS0002', 'TTS_DoubaoTTS0003', 'TTS_DoubaoTTS0004', 'TTS_DoubaoTTS0005'); +INSERT INTO `ai_tts_voice` VALUES ('TTS_DoubaoTTS0001', 'TTS_DoubaoTTS', '通用女声', 'BV001_streaming', '普通话', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV001.mp3', NULL, 3, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_DoubaoTTS0002', 'TTS_DoubaoTTS', '通用男声', 'BV002_streaming', '普通话', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV002.mp3', NULL, 2, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_DoubaoTTS0003', 'TTS_DoubaoTTS', '阳光男生', 'BV056_streaming', '普通话', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV056.mp3', NULL, 4, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_DoubaoTTS0004', 'TTS_DoubaoTTS', '奶气萌娃', 'BV051_streaming', '普通话', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV051.mp3', NULL, 5, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_DoubaoTTS0005', 'TTS_DoubaoTTS', '湾湾小何', 'zh_female_wanwanxiaohe_moon_bigtts', '普通话', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/%E6%B9%BE%E6%B9%BE%E5%B0%8F%E4%BD%95.mp3', NULL, 6, NULL, NULL, NULL, NULL); + +-- 修正CosyVoiceSiliconflow音色 +delete from `ai_tts_voice` where id in ('TTS_CosyVoiceSiliconflow0001', 'TTS_CosyVoiceSiliconflow0002'); +INSERT INTO `ai_tts_voice` VALUES ('TTS_CosyVoiceSiliconflow0001', 'TTS_CosyVoiceSiliconflow', 'CosyVoice男声', 'FunAudioLLM/CosyVoice2-0.5B:alex', '中文', NULL, NULL, 6, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_CosyVoiceSiliconflow0002', 'TTS_CosyVoiceSiliconflow', 'CosyVoice女声', 'FunAudioLLM/CosyVoice2-0.5B:bella', '中文', NULL, NULL, 6, NULL, NULL, NULL, NULL); + +-- CozeCnTTS音色 +delete from `ai_tts_voice` where id = 'TTS_CozeCnTTS0001'; +INSERT INTO `ai_tts_voice` VALUES ('TTS_CozeCnTTS0001', 'TTS_CozeCnTTS', 'CozeCn音色', '7426720361733046281', '中文', NULL, NULL, 7, NULL, NULL, NULL, NULL); + +-- MinimaxTTS音色 +delete from `ai_tts_voice` where id = 'TTS_MinimaxTTS0001'; +INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxTTS0001', 'TTS_MinimaxTTS', 'Minimax少女音', 'female-shaonv', '中文', NULL, NULL, 8, NULL, NULL, NULL, NULL); + +-- AliyunTTS音色 +delete from `ai_tts_voice` where id = 'TTS_AliyunTTS0001'; +INSERT INTO `ai_tts_voice` VALUES ('TTS_AliyunTTS0001', 'TTS_AliyunTTS', '阿里云小云', 'xiaoyun', '中文', NULL, NULL, 9, NULL, NULL, NULL, NULL); + +-- TTS302AI音色 +delete from `ai_tts_voice` where id = 'TTS_TTS302AI0001'; +INSERT INTO `ai_tts_voice` VALUES ('TTS_TTS302AI0001', 'TTS_TTS302AI', '湾湾小何', 'zh_female_wanwanxiaohe_moon_bigtts', '中文', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/%E6%B9%BE%E6%B9%BE%E5%B0%8F%E4%BD%95.mp3', NULL, 10, NULL, NULL, NULL, NULL); + +-- GizwitsTTS音色 +delete from `ai_tts_voice` where id = 'TTS_GizwitsTTS0001'; +INSERT INTO `ai_tts_voice` VALUES ('TTS_GizwitsTTS0001', 'TTS_GizwitsTTS', '机智云湾湾', 'zh_female_wanwanxiaohe_moon_bigtts', '中文', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/%E6%B9%BE%E6%B9%BE%E5%B0%8F%E4%BD%95.mp3', NULL, 11, NULL, NULL, NULL, NULL); + +-- ACGNTTS音色 +delete from `ai_tts_voice` where id = 'TTS_ACGNTTS0001'; +INSERT INTO `ai_tts_voice` VALUES ('TTS_ACGNTTS0001', 'TTS_ACGNTTS', 'ACG音色', '1695', '中文', NULL, NULL, 12, NULL, NULL, NULL, NULL); + +-- OpenAITTS音色 +delete from `ai_tts_voice` where id = 'TTS_OpenAITTS0001'; +INSERT INTO `ai_tts_voice` VALUES ('TTS_OpenAITTS0001', 'TTS_OpenAITTS', 'OpenAI男声', 'onyx', '中文', NULL, NULL, 13, NULL, NULL, NULL, NULL); + +-- 添加腾讯语音合成音色 +delete from `ai_tts_voice` where id = 'TTS_TencentTTS0001'; +INSERT INTO `ai_tts_voice` VALUES ('TTS_TencentTTS0001', 'TTS_TencentTTS', '智瑜', '101001', '中文', NULL, NULL, 1, NULL, NULL, NULL, NULL); + +-- 其他音色 +delete from `ai_tts_voice` where id = 'TTS_FishSpeech0000'; +INSERT INTO `ai_tts_voice` VALUES ('TTS_FishSpeech0000', 'TTS_FishSpeech', '', '', '中文', '', NULL, 8, NULL, NULL, NULL, NULL); + +delete from `ai_tts_voice` where id = 'TTS_GPT_SOVITS_V20000'; +INSERT INTO `ai_tts_voice` VALUES ('TTS_GPT_SOVITS_V20000', 'TTS_GPT_SOVITS_V2', '', '', '中文', '', NULL, 8, NULL, NULL, NULL, NULL); + +delete from `ai_tts_voice` where id in ('TTS_GPT_SOVITS_V30000', 'TTS_CustomTTS0000'); +INSERT INTO `ai_tts_voice` VALUES ('TTS_GPT_SOVITS_V30000', 'TTS_GPT_SOVITS_V3', '', '', '中文', '', NULL, 8, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_CustomTTS0000', 'TTS_CustomTTS', '', '', '中文', '', NULL, 8, NULL, NULL, NULL, NULL); + diff --git a/main/manager-api/src/main/resources/db/changelog/202504221555.sql b/main/manager-api/src/main/resources/db/changelog/202504221555.sql new file mode 100644 index 00000000..e80a8a8d --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202504221555.sql @@ -0,0 +1,49 @@ +-- 添加火山引擎边缘大模型网关(LLM + TTS) +INSERT INTO `ai_model_config` VALUES ('LLM_VolcesAiGatewayLLM', 'LLM', 'VolcesAiGatewayTTS', '火山引擎边缘大模型网关', 0, 1, '{\"type\": \"openai\", \"base_url\": \"https://ai-gateway.vei.volces.com/v1\", \"model_name\": \"doubao-pro-32k-functioncall\", \"api_key\": \"你的网关访问密钥\"}', NULL, NULL, 14, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('TTS_VolcesAiGatewayTTS', 'TTS', 'VolcesAiGatewayTTS', '火山引擎边缘大模型网关', 0, 1, '{\"type\": \"openai\", \"api_key\": \"你的网关访问密钥\", \"api_url\": \"https://ai-gateway.vei.volces.com/v1/audio/speech\", \"model\": \"doubao-tts\", \"voice\": \"zh_male_shaonianzixin_moon_bigtts\", \"speed\": 1, \"output_dir\": \"tmp/\"}', NULL, NULL, 15, NULL, NULL, NULL, NULL); +-- 添加火山引擎边缘大模型网关语音合成音色 +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0001', 'TTS_VolcesAiGatewayTTS', '灿灿/Shiny', 'zh_female_cancan_mars_bigtts', '中文、美式英语', NULL, NULL, 1, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0002', 'TTS_VolcesAiGatewayTTS', '清新女声', 'zh_female_qingxinnvsheng_mars_bigtts', '中文', NULL, NULL, 2, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0003', 'TTS_VolcesAiGatewayTTS', '爽快思思/Skye', 'zh_female_shuangkuaisisi_moon_bigtts', '中文、美式英语', NULL, NULL, 3, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0004', 'TTS_VolcesAiGatewayTTS', '温暖阿虎/Alvin', 'zh_male_wennuanahu_moon_bigtts', '中文、美式英语', NULL, NULL, 4, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0005', 'TTS_VolcesAiGatewayTTS', '少年梓辛/Brayan', 'zh_male_shaonianzixin_moon_bigtts', '中文、美式英语', NULL, NULL, 5, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0006', 'TTS_VolcesAiGatewayTTS', '知性女声', 'zh_female_zhixingnvsheng_mars_bigtts', '中文', NULL, NULL, 6, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0007', 'TTS_VolcesAiGatewayTTS', '清爽男大', 'zh_male_qingshuangnanda_mars_bigtts', '中文', NULL, NULL, 7, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0008', 'TTS_VolcesAiGatewayTTS', '邻家女孩', 'zh_female_linjianvhai_moon_bigtts', '中文', NULL, NULL, 8, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0009', 'TTS_VolcesAiGatewayTTS', '渊博小叔', 'zh_male_yuanboxiaoshu_moon_bigtts', '中文', NULL, NULL, 9, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0010', 'TTS_VolcesAiGatewayTTS', '阳光青年', 'zh_male_yangguangqingnian_moon_bigtts', '中文', NULL, NULL, 10, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0011', 'TTS_VolcesAiGatewayTTS', '甜美小源', 'zh_female_tianmeixiaoyuan_moon_bigtts', '中文', NULL, NULL, 11, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0012', 'TTS_VolcesAiGatewayTTS', '清澈梓梓', 'zh_female_qingchezizi_moon_bigtts', '中文', NULL, NULL, 12, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0013', 'TTS_VolcesAiGatewayTTS', '解说小明', 'zh_male_jieshuoxiaoming_moon_bigtts', '中文', NULL, NULL, 13, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0014', 'TTS_VolcesAiGatewayTTS', '开朗姐姐', 'zh_female_kailangjiejie_moon_bigtts', '中文', NULL, NULL, 14, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0015', 'TTS_VolcesAiGatewayTTS', '邻家男孩', 'zh_male_linjiananhai_moon_bigtts', '中文', NULL, NULL, 15, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0016', 'TTS_VolcesAiGatewayTTS', '甜美悦悦', 'zh_female_tianmeiyueyue_moon_bigtts', '中文', NULL, NULL, 16, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0017', 'TTS_VolcesAiGatewayTTS', '心灵鸡汤', 'zh_female_xinlingjitang_moon_bigtts', '中文', NULL, NULL, 17, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0018', 'TTS_VolcesAiGatewayTTS', '知性温婉', 'ICL_zh_female_zhixingwenwan_tob', '中文', NULL, NULL, 18, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0019', 'TTS_VolcesAiGatewayTTS', '暖心体贴', 'ICL_zh_male_nuanxintitie_tob', '中文', NULL, NULL, 19, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0020', 'TTS_VolcesAiGatewayTTS', '温柔文雅', 'ICL_zh_female_wenrouwenya_tob', '中文', NULL, NULL, 20, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0021', 'TTS_VolcesAiGatewayTTS', '开朗轻快', 'ICL_zh_male_kailangqingkuai_tob', '中文', NULL, NULL, 21, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0022', 'TTS_VolcesAiGatewayTTS', '活泼爽朗', 'ICL_zh_male_huoposhuanglang_tob', '中文', NULL, NULL, 22, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0023', 'TTS_VolcesAiGatewayTTS', '率真小伙', 'ICL_zh_male_shuaizhenxiaohuo_tob', '中文', NULL, NULL, 23, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0024', 'TTS_VolcesAiGatewayTTS', '温柔小哥', 'zh_male_wenrouxiaoge_mars_bigtts', '中文', NULL, NULL, 24, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0025', 'TTS_VolcesAiGatewayTTS', 'Smith', 'en_male_smith_mars_bigtts', '英式英语', NULL, NULL, 25, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0026', 'TTS_VolcesAiGatewayTTS', 'Anna', 'en_female_anna_mars_bigtts', '英式英语', NULL, NULL, 26, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0027', 'TTS_VolcesAiGatewayTTS', 'Adam', 'en_male_adam_mars_bigtts', '美式英语', NULL, NULL, 27, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0028', 'TTS_VolcesAiGatewayTTS', 'Sarah', 'en_female_sarah_mars_bigtts', '澳洲英语', NULL, NULL, 28, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0029', 'TTS_VolcesAiGatewayTTS', 'Dryw', 'en_male_dryw_mars_bigtts', '澳洲英语', NULL, NULL, 29, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0030', 'TTS_VolcesAiGatewayTTS', 'かずね(和音)', 'multi_male_jingqiangkanye_moon_bigtts', '日语、西语', NULL, NULL, 30, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0031', 'TTS_VolcesAiGatewayTTS', 'はるこ(晴子)', 'multi_female_shuangkuaisisi_moon_bigtts', '日语、西语', NULL, NULL, 31, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0032', 'TTS_VolcesAiGatewayTTS', 'ひろし(広志)', 'multi_male_wanqudashu_moon_bigtts', '日语、西语', NULL, NULL, 32, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0033', 'TTS_VolcesAiGatewayTTS', 'あけみ(朱美)', 'multi_female_gaolengyujie_moon_bigtts', '日语', NULL, NULL, 33, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0034', 'TTS_VolcesAiGatewayTTS', 'Amanda', 'en_female_amanda_mars_bigtts', '美式英语', NULL, NULL, 34, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0035', 'TTS_VolcesAiGatewayTTS', 'Jackson', 'en_male_jackson_mars_bigtts', '美式英语', NULL, NULL, 35, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0036', 'TTS_VolcesAiGatewayTTS', '京腔侃爷/Harmony', 'zh_male_jingqiangkanye_moon_bigtts', '中文-北京口音、英文', NULL, NULL, 36, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0037', 'TTS_VolcesAiGatewayTTS', '湾湾小何', 'zh_female_wanwanxiaohe_moon_bigtts', '中文-台湾口音', NULL, NULL, 37, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0038', 'TTS_VolcesAiGatewayTTS', '湾区大叔', 'zh_female_wanqudashu_moon_bigtts', '中文-广东口音', NULL, NULL, 38, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0039', 'TTS_VolcesAiGatewayTTS', '呆萌川妹', 'zh_female_daimengchuanmei_moon_bigtts', '中文-四川口音', NULL, NULL, 39, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0040', 'TTS_VolcesAiGatewayTTS', '广州德哥', 'zh_male_guozhoudege_moon_bigtts', '中文-广东口音', NULL, NULL, 40, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0041', 'TTS_VolcesAiGatewayTTS', '北京小爷', 'zh_male_beijingxiaoye_moon_bigtts', '中文-北京口音', NULL, NULL, 41, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0042', 'TTS_VolcesAiGatewayTTS', '浩宇小哥', 'zh_male_haoyuxiaoge_moon_bigtts', '中文-青岛口音', NULL, NULL, 42, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0043', 'TTS_VolcesAiGatewayTTS', '广西远舟', 'zh_male_guangxiyuanzhou_moon_bigtts', '中文-广西口音', NULL, NULL, 43, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0044', 'TTS_VolcesAiGatewayTTS', '妹坨洁儿', 'zh_female_meituojieer_moon_bigtts', '中文-长沙口音', NULL, NULL, 44, NULL, NULL, NULL, NULL); +INSERT INTO `ai_tts_voice` VALUES ('TTS_VolcesAiGatewayTTS_0045', 'TTS_VolcesAiGatewayTTS', '豫州子轩', 'zh_male_yuzhouzixuan_moon_bigtts', '中文-河南口音', NULL, NULL, 45, NULL, NULL, NULL, NULL); \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/202504251422.sql b/main/manager-api/src/main/resources/db/changelog/202504251422.sql new file mode 100644 index 00000000..88e03fea --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202504251422.sql @@ -0,0 +1,30 @@ +-- 增加server.ota,用于配置ota地址 + +delete from `sys_params` where id = 100; +delete from `sys_params` where id = 101; + +delete from `sys_params` where id = 106; +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (106, 'server.websocket', 'null', 'string', 1, 'websocket地址,多个用;分隔'); + +delete from `sys_params` where id = 107; +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (107, 'server.ota', 'null', 'string', 1, 'ota地址'); + + +-- 增加固件信息表 +CREATE TABLE IF NOT EXISTS `ai_ota` ( + `id` varchar(32) NOT NULL COMMENT 'ID', + `firmware_name` varchar(100) DEFAULT NULL COMMENT '固件名称', + `type` varchar(50) DEFAULT NULL COMMENT '固件类型', + `version` varchar(50) DEFAULT NULL COMMENT '版本号', + `size` bigint DEFAULT NULL COMMENT '文件大小(字节)', + `remark` varchar(500) DEFAULT NULL COMMENT '备注/说明', + `firmware_path` varchar(255) DEFAULT NULL COMMENT '固件路径', + `sort` int unsigned DEFAULT '0' COMMENT '排序', + `updater` bigint DEFAULT NULL COMMENT '更新者', + `update_date` datetime DEFAULT NULL COMMENT '更新时间', + `creator` bigint DEFAULT NULL COMMENT '创建者', + `create_date` datetime DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='固件信息表'; + +update ai_device set auto_update = 1; diff --git a/main/manager-api/src/main/resources/db/changelog/202504291043.sql b/main/manager-api/src/main/resources/db/changelog/202504291043.sql new file mode 100644 index 00000000..555b8cf4 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202504291043.sql @@ -0,0 +1,504 @@ +-- 增加FunASR服务语音识别模型供应器和配置 +DELETE FROM `ai_model_provider` WHERE `id` = 'SYSTEM_ASR_FunASRServer'; +INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES +('SYSTEM_ASR_FunASRServer', 'ASR', 'fun_server', 'FunASR服务语音识别', '[{"key":"host","label":"服务地址","type":"string"},{"key":"port","label":"端口号","type":"number"}]', 4, 1, NOW(), 1, NOW()); + +DELETE FROM `ai_model_config` WHERE `id` = 'ASR_FunASRServer'; +INSERT INTO `ai_model_config` VALUES ('ASR_FunASRServer', 'ASR', 'FunASRServer', 'FunASR服务语音识别', 0, 1, '{\"type\": \"fun_server\", \"host\": \"127.0.0.1\", \"port\": 10096}', NULL, NULL, 5, NULL, NULL, NULL, NULL); + +-- 修改ai_model_config表的remark字段类型为TEXT +ALTER TABLE `ai_model_config` MODIFY COLUMN `remark` TEXT COMMENT '备注'; + +-- 更新ASR模型配置的说明文档 +UPDATE `ai_model_config` SET +`doc_link` = 'https://github.com/modelscope/FunASR/blob/main/runtime/docs/SDK_advanced_guide_online_zh.md', +`remark` = '独立部署FunASR,使用FunASR的API服务,只需要五句话 +第一句:mkdir -p ./funasr-runtime-resources/models +第二句:sudo docker run -d -p 10096:10095 --privileged=true -v $PWD/funasr-runtime-resources/models:/workspace/models registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-online-cpu-0.1.12 +上一句话执行后会进入到容器,继续第三句:cd FunASR/runtime +不要退出容器,继续在容器中执行第四句:nohup bash run_server_2pass.sh --download-model-dir /workspace/models --vad-dir damo/speech_fsmn_vad_zh-cn-16k-common-onnx --model-dir damo/speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-onnx --online-model-dir damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online-onnx --punc-dir damo/punc_ct-transformer_zh-cn-common-vad_realtime-vocab272727-onnx --lm-dir damo/speech_ngram_lm_zh-cn-ai-wesp-fst --itn-dir thuduj12/fst_itn_zh --hotword /workspace/models/hotwords.txt > log.txt 2>&1 & +上一句话执行后会进入到容器,继续第五句:tail -f log.txt +第五句话执行完后,会看到模型下载日志,下载完后就可以连接使用了 +以上是使用CPU推理,如果有GPU,详细参考:https://github.com/modelscope/FunASR/blob/main/runtime/docs/SDK_advanced_guide_online_zh.md' WHERE `id` = 'ASR_FunASRServer'; + +-- 更新FunASR本地模型配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://github.com/modelscope/FunASR', +`remark` = 'FunASR本地模型配置说明: +1. 需要下载模型文件到xiaozhi-server/models/SenseVoiceSmall目录 +2. 支持中日韩粤语音识别 +3. 本地推理,无需网络连接 +4. 待识别文件保存在tmp/目录' WHERE `id` = 'ASR_FunASR'; + +-- 更新SherpaASR配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://github.com/k2-fsa/sherpa-onnx', +`remark` = 'SherpaASR配置说明: +1. 运行时自动下载模型文件到models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17目录 +2. 支持中文、英文、日语、韩语、粤语等多种语言 +3. 本地推理,无需网络连接 +4. 输出文件保存在tmp/目录' WHERE `id` = 'ASR_SherpaASR'; + +-- 更新豆包ASR配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://console.volcengine.com/speech/app', +`remark` = '豆包ASR配置说明: +1. 需要在火山引擎控制台创建应用并获取appid和access_token +2. 支持中文语音识别 +3. 需要网络连接 +4. 输出文件保存在tmp/目录 +申请步骤: +1. 访问 https://console.volcengine.com/speech/app +2. 创建新应用 +3. 获取appid和access_token +4. 填入配置文件中' WHERE `id` = 'ASR_DoubaoASR'; + +-- 更新腾讯ASR配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://console.cloud.tencent.com/cam/capi', +`remark` = '腾讯ASR配置说明: +1. 需要在腾讯云控制台创建应用并获取appid、secret_id和secret_key +2. 支持中文语音识别 +3. 需要网络连接 +4. 输出文件保存在tmp/目录 +申请步骤: +1. 访问 https://console.cloud.tencent.com/cam/capi 获取密钥 +2. 访问 https://console.cloud.tencent.com/asr/resourcebundle 领取免费资源 +3. 获取appid、secret_id和secret_key +4. 填入配置文件中' WHERE `id` = 'ASR_TencentASR'; + +-- 更新TTS模型配置说明 +-- EdgeTTS配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://github.com/rany2/edge-tts', +`remark` = 'EdgeTTS配置说明: +1. 使用微软Edge TTS服务 +2. 支持多种语言和音色 +3. 免费使用,无需注册 +4. 需要网络连接 +5. 输出文件保存在tmp/目录' WHERE `id` = 'TTS_EdgeTTS'; + +-- 豆包TTS配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://console.volcengine.com/speech/service/8', +`remark` = '豆包TTS配置说明: +1. 访问 https://console.volcengine.com/speech/service/8 +2. 需要在火山引擎控制台创建应用并获取appid和access_token +3. 山引擎语音一定要购买花钱,起步价30元,就有100并发了。如果用免费的只有2个并发,会经常报tts错误 +4. 购买服务后,购买免费的音色后,可能要等半小时左右,才能使用。 +5. 填入配置文件中' WHERE `id` = 'TTS_DoubaoTTS'; + +-- 硅基流动TTS配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://cloud.siliconflow.cn/account/ak', +`remark` = '硅基流动TTS配置说明: +1. 访问 https://cloud.siliconflow.cn/account/ak +2. 注册并获取API密钥 +3. 填入配置文件中' WHERE `id` = 'TTS_CosyVoiceSiliconflow'; + +-- Coze中文TTS配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://www.coze.cn/open/oauth/pats', +`remark` = 'Coze中文TTS配置说明: +1. 访问 https://www.coze.cn/open/oauth/pats +2. 获取个人令牌 +3. 填入配置文件中' WHERE `id` = 'TTS_CozeCnTTS'; + +-- FishSpeech配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://github.com/fishaudio/fish-speech', +`remark` = 'FishSpeech配置说明: +1. 需要本地部署FishSpeech服务 +2. 支持自定义音色 +3. 本地推理,无需网络连接 +4. 输出文件保存在tmp/目录 +5. 运行服务示例命令:python -m tools.api_server --listen 0.0.0.0:8080 --llama-checkpoint-path "checkpoints/fish-speech-1.5" --decoder-checkpoint-path "checkpoints/fish-speech-1.5/firefly-gan-vq-fsq-8x1024-21hz-generator.pth" --decoder-config-name firefly_gan_vq --compile' WHERE `id` = 'TTS_FishSpeech'; + +-- GPT-SoVITS V2配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://github.com/RVC-Boss/GPT-SoVITS', +`remark` = 'GPT-SoVITS V2配置说明: +1. 需要本地部署GPT-SoVITS服务 +2. 支持自定义音色克隆 +3. 本地推理,无需网络连接 +4. 输出文件保存在tmp/目录 +部署步骤: +1. 运行服务示例命令:python api_v2.py -a 127.0.0.1 -p 9880 -c GPT_SoVITS/configs/demo.yaml' WHERE `id` = 'TTS_GPT_SOVITS_V2'; + +-- GPT-SoVITS V3配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://github.com/RVC-Boss/GPT-SoVITS', +`remark` = 'GPT-SoVITS V3配置说明: +1. 需要本地部署GPT-SoVITS V3服务 +2. 支持自定义音色克隆 +3. 本地推理,无需网络连接 +4. 输出文件保存在tmp/目录' WHERE `id` = 'TTS_GPT_SOVITS_V3'; + +-- MiniMax TTS配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://platform.minimaxi.com/', +`remark` = 'MiniMax TTS配置说明: +1. 需要在MiniMax平台创建账户并充值 +2. 支持多种音色,当前配置使用female-shaonv +3. 需要网络连接 +4. 输出文件保存在tmp/目录 +申请步骤: +1. 访问 https://platform.minimaxi.com/ 注册账号 +2. 访问 https://platform.minimaxi.com/user-center/payment/balance 充值 +3. 访问 https://platform.minimaxi.com/user-center/basic-information 获取group_id +4. 访问 https://platform.minimaxi.com/user-center/basic-information/interface-key 获取api_key +5. 填入配置文件中' WHERE `id` = 'TTS_MinimaxTTS'; + +-- 阿里云TTS配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://nls-portal.console.aliyun.com/', +`remark` = '阿里云TTS配置说明: +1. 需要在阿里云平台开通智能语音交互服务 +2. 支持多种音色,当前配置使用xiaoyun +3. 需要网络连接 +4. 输出文件保存在tmp/目录 +申请步骤: +1. 访问 https://nls-portal.console.aliyun.com/ 开通服务 +2. 访问 https://nls-portal.console.aliyun.com/applist 获取appkey +3. 访问 https://nls-portal.console.aliyun.com/overview 获取token +4. 填入配置文件中 +注意:token是临时的24小时有效,长期使用需要配置access_key_id和access_key_secret' WHERE `id` = 'TTS_AliyunTTS'; + +-- 腾讯TTS配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://console.cloud.tencent.com/cam/capi', +`remark` = '腾讯TTS配置说明: +1. 需要在腾讯云平台开通智能语音交互服务 +2. 支持多种音色,当前配置使用101001 +3. 需要网络连接 +4. 输出文件保存在tmp/目录 +申请步骤: +1. 访问 https://console.cloud.tencent.com/cam/capi 获取密钥 +2. 访问 https://console.cloud.tencent.com/tts/resourcebundle 领取免费资源 +3. 创建新应用 +4. 获取appid、secret_id和secret_key +5. 填入配置文件中' WHERE `id` = 'TTS_TencentTTS'; + +-- 302AI TTS配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://dash.302.ai/', +`remark` = '302AI TTS配置说明: +1. 需要在302平台创建账户并获取API密钥 +2. 支持多种音色,当前配置使用湾湾小何音色 +3. 需要网络连接 +4. 输出文件保存在tmp/目录 +申请步骤: +1. 访问 https://dash.302.ai/ 注册账号 +2. 访问 https://dash.302.ai/apis/list 获取API密钥 +3. 填入配置文件中 +价格:$35/百万字符' WHERE `id` = 'TTS_TTS302AI'; + +-- 机智云TTS配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://agentrouter.gizwitsapi.com/panel/token', +`remark` = '机智云TTS配置说明: +1. 需要在机智云平台获取API密钥 +2. 支持多种音色,当前配置使用湾湾小何音色 +3. 需要网络连接 +4. 输出文件保存在tmp/目录 +申请步骤: +1. 访问 https://agentrouter.gizwitsapi.com/panel/token 获取API密钥 +2. 填入配置文件中 +注意:前一万名注册的用户,将送5元体验金额' WHERE `id` = 'TTS_GizwitsTTS'; + +-- ACGN TTS配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://acgn.ttson.cn/', +`remark` = 'ACGN TTS配置说明: +1. 需要在ttson平台购买token +2. 支持多种角色音色,当前配置使用角色ID:1695 +3. 需要网络连接 +4. 输出文件保存在tmp/目录 +申请步骤: +1. 访问 https://acgn.ttson.cn/ 查看角色列表 +2. 访问 www.ttson.cn 购买token +3. 填入配置文件中 +开发相关疑问请提交至网站上的qq' WHERE `id` = 'TTS_ACGNTTS'; + +-- OpenAI TTS配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://platform.openai.com/api-keys', +`remark` = 'OpenAI TTS配置说明: +1. 需要在OpenAI平台获取API密钥 +2. 支持多种音色,当前配置使用onyx +3. 需要网络连接 +4. 输出文件保存在tmp/目录 +申请步骤: +1. 访问 https://platform.openai.com/api-keys 获取API密钥 +2. 填入配置文件中 +注意:国内需要使用代理访问' WHERE `id` = 'TTS_OpenAITTS'; + +-- 自定义TTS配置说明 +UPDATE `ai_model_config` SET +`doc_link` = NULL, +`remark` = '自定义TTS配置说明: +1. 支持自定义TTS接口服务 +2. 使用GET方式请求 +3. 需要网络连接 +4. 输出文件保存在tmp/目录 +配置说明: +1. 在params中配置请求参数 +2. 在headers中配置请求头 +3. 设置返回音频格式' WHERE `id` = 'TTS_CustomTTS'; + +-- 火山引擎边缘大模型网关TTS配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://console.volcengine.com/vei/aigateway/', +`remark` = '火山引擎边缘大模型网关TTS配置说明: +1. 访问 https://console.volcengine.com/vei/aigateway/ +2. 创建网关访问密钥,搜索并勾选 Doubao-语音合成 +3. 如果需要使用LLM,一并勾选 Doubao-pro-32k-functioncall +4. 访问 https://console.volcengine.com/vei/aigateway/tokens-list 获取密钥 +5. 填入配置文件中 +音色列表参考:https://www.volcengine.com/docs/6561/1257544' WHERE `id` = 'TTS_VolcesAiGatewayTTS'; + +-- 更新LLM模型配置说明 +-- ChatGLM配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://bigmodel.cn/usercenter/proj-mgmt/apikeys', +`remark` = 'ChatGLM配置说明: +1. 访问 https://bigmodel.cn/usercenter/proj-mgmt/apikeys +2. 注册并获取API密钥 +3. 填入配置文件中' WHERE `id` = 'LLM_ChatGLMLLM'; + +-- Ollama配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://ollama.com/', +`remark` = 'Ollama配置说明: +1. 安装Ollama服务 +2. 运行命令:ollama pull qwen2.5 +3. 确保服务运行在http://localhost:11434' WHERE `id` = 'LLM_OllamaLLM'; + +-- 通义千问配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://bailian.console.aliyun.com/?apiKey=1#/api-key', +`remark` = '通义千问配置说明: +1. 访问 https://bailian.console.aliyun.com/?apiKey=1#/api-key +2. 获取API密钥 +3. 填入配置文件中,当前配置使用qwen-turbo模型 +4. 支持自定义参数:temperature=0.7, max_tokens=500, top_p=1, top_k=50' WHERE `id` = 'LLM_AliLLM'; + +-- 通义百炼配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://bailian.console.aliyun.com/?apiKey=1#/api-key', +`remark` = '通义百炼配置说明: +1. 访问 https://bailian.console.aliyun.com/?apiKey=1#/api-key +2. 获取app_id和api_key +3. 填入配置文件中' WHERE `id` = 'LLM_AliAppLLM'; + +-- 豆包大模型配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://console.volcengine.com/ark/region:ark+cn-beijing/openManagement', +`remark` = '豆包大模型配置说明: +1. 访问 https://console.volcengine.com/ark/region:ark+cn-beijing/openManagement +2. 开通Doubao-1.5-pro服务 +3. 访问 https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey 获取API密钥 +4. 填入配置文件中 +5. 当前建议使用doubao-1-5-pro-32k-250115 +注意:有免费额度500000token' WHERE `id` = 'LLM_DoubaoLLM'; + +-- DeepSeek配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://platform.deepseek.com/', +`remark` = 'DeepSeek配置说明: +1. 访问 https://platform.deepseek.com/ +2. 注册并获取API密钥 +3. 填入配置文件中' WHERE `id` = 'LLM_DeepSeekLLM'; + +-- Dify配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://cloud.dify.ai/', +`remark` = 'Dify配置说明: +1. 访问 https://cloud.dify.ai/ +2. 注册并获取API密钥 +3. 填入配置文件中 +4. 支持多种对话模式:workflows/run, chat-messages, completion-messages +5. 平台设置的角色定义会失效,需要在Dify控制台设置 +注意:建议使用本地部署的Dify接口,国内部分区域访问公有云接口可能受限' WHERE `id` = 'LLM_DifyLLM'; + +-- Gemini配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://aistudio.google.com/apikey', +`remark` = 'Gemini配置说明: +1. 使用谷歌Gemini API服务 +2. 当前配置使用gemini-2.0-flash模型 +3. 需要网络连接 +4. 支持配置代理 +申请步骤: +1. 访问 https://aistudio.google.com/apikey +2. 创建API密钥 +3. 填入配置文件中 +注意:若在中国境内使用,请遵守《生成式人工智能服务管理暂行办法》' WHERE `id` = 'LLM_GeminiLLM'; + +-- Coze配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://www.coze.cn/open/oauth/pats', +`remark` = 'Coze配置说明: +1. 使用Coze平台服务 +2. 需要bot_id、user_id和个人令牌 +3. 需要网络连接 +申请步骤: +1. 访问 https://www.coze.cn/open/oauth/pats +2. 获取个人令牌 +3. 手动计算bot_id和user_id +4. 填入配置文件中' WHERE `id` = 'LLM_CozeLLM'; + +-- LM Studio配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://lmstudio.ai/', +`remark` = 'LM Studio配置说明: +1. 使用本地部署的LM Studio服务 +2. 当前配置使用deepseek-r1-distill-llama-8b@q4_k_m模型 +3. 本地推理,无需网络连接 +4. 需要预先下载模型 +部署步骤: +1. 安装LM Studio +2. 从社区下载模型 +3. 确保服务运行在http://localhost:1234/v1' WHERE `id` = 'LLM_LMStudioLLM'; + +-- FastGPT配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://cloud.tryfastgpt.ai/account/apikey', +`remark` = 'FastGPT配置说明: +1. 使用FastGPT平台服务 +2. 需要网络连接 +3. 配置文件中的prompt无效,需要在FastGPT控制台设置 +4. 支持自定义变量 +申请步骤: +1. 访问 https://cloud.tryfastgpt.ai/account/apikey +2. 获取API密钥 +3. 填入配置文件中' WHERE `id` = 'LLM_FastgptLLM'; + +-- Xinference配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://github.com/xorbitsai/inference', +`remark` = 'Xinference配置说明: +1. 使用本地部署的Xinference服务 +2. 当前配置使用qwen2.5:72b-AWQ模型 +3. 本地推理,无需网络连接 +4. 需要预先启动对应模型 +部署步骤: +1. 安装Xinference +2. 启动服务并加载模型 +3. 确保服务运行在http://localhost:9997' WHERE `id` = 'LLM_XinferenceLLM'; + +-- Xinference小模型配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://github.com/xorbitsai/inference', +`remark` = 'Xinference小模型配置说明: +1. 使用本地部署的Xinference服务 +2. 当前配置使用qwen2.5:3b-AWQ模型 +3. 本地推理,无需网络连接 +4. 用于意图识别 +部署步骤: +1. 安装Xinference +2. 启动服务并加载模型 +3. 确保服务运行在http://localhost:9997' WHERE `id` = 'LLM_XinferenceSmallLLM'; + +-- 火山引擎边缘大模型网关LLM配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://console.volcengine.com/vei/aigateway/', +`remark` = '火山引擎边缘大模型网关LLM配置说明: +1. 使用火山引擎边缘大模型网关服务 +2. 需要网关访问密钥 +3. 需要网络连接 +4. 支持function_call功能 +申请步骤: +1. 访问 https://console.volcengine.com/vei/aigateway/ +2. 创建网关访问密钥,搜索并勾选 Doubao-pro-32k-functioncall +3. 如果需要使用语音合成,一并勾选 Doubao-语音合成 +4. 访问 https://console.volcengine.com/vei/aigateway/tokens-list 获取密钥 +5. 填入配置文件中' WHERE `id` = 'LLM_VolcesAiGatewayLLM'; + +-- 更新Memory模型配置说明 +-- 无记忆配置说明 +UPDATE `ai_model_config` SET +`doc_link` = NULL, +`remark` = '无记忆配置说明: +1. 不保存对话历史 +2. 每次对话都是独立的 +3. 无需额外配置 +4. 适合对隐私要求高的场景' WHERE `id` = 'Memory_nomem'; + +-- 本地短期记忆配置说明 +UPDATE `ai_model_config` SET +`doc_link` = NULL, +`remark` = '本地短期记忆配置说明: +1. 使用本地存储保存对话历史 +2. 通过selected_module的llm总结对话内容 +3. 数据保存在本地,不会上传到服务器 +4. 适合注重隐私的场景 +5. 无需额外配置' WHERE `id` = 'Memory_mem_local_short'; + +-- Mem0AI记忆配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://app.mem0.ai/dashboard/api-keys', +`remark` = 'Mem0AI记忆配置说明: +1. 使用Mem0AI服务保存对话历史 +2. 需要API密钥 +3. 需要网络连接 +4. 每月有1000次免费调用 +申请步骤: +1. 访问 https://app.mem0.ai/dashboard/api-keys +2. 获取API密钥 +3. 填入配置文件中' WHERE `id` = 'Memory_mem0ai'; + +-- 更新Intent模型配置说明 +-- 无意图识别配置说明 +UPDATE `ai_model_config` SET +`doc_link` = NULL, +`remark` = '无意图识别配置说明: +1. 不进行意图识别 +2. 所有对话直接传递给LLM处理 +3. 无需额外配置 +4. 适合简单对话场景' WHERE `id` = 'Intent_nointent'; + +-- LLM意图识别配置说明 +UPDATE `ai_model_config` SET +`doc_link` = NULL, +`remark` = 'LLM意图识别配置说明: +1. 使用独立的LLM进行意图识别 +2. 默认使用selected_module.LLM的模型 +3. 可以配置使用独立的LLM(如免费的ChatGLMLLM) +4. 通用性强,但会增加处理时间 +5. 不支持控制音量大小等iot操作 +配置说明: +1. 在llm字段中指定使用的LLM模型 +2. 如果不指定,则使用selected_module.LLM的模型' WHERE `id` = 'Intent_intent_llm'; + +-- 函数调用意图识别配置说明 +UPDATE `ai_model_config` SET +`doc_link` = NULL, +`remark` = '函数调用意图识别配置说明: +1. 使用LLM的function_call功能进行意图识别 +2. 需要所选择的LLM支持function_call +3. 按需调用工具,处理速度快 +4. 支持所有iot指令 +5. 默认已加载以下功能: + - handle_exit_intent(退出识别) + - play_music(音乐播放) + - change_role(角色切换) + - get_weather(天气查询) + - get_news(新闻查询) +配置说明: +1. 在functions字段中配置需要加载的功能模块 +2. 系统默认已加载基础功能,无需重复配置 +3. 可以添加自定义功能模块' WHERE `id` = 'Intent_function_call'; + +-- 更新VAD模型配置说明 +-- SileroVAD配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://github.com/snakers4/silero-vad', +`remark` = 'SileroVAD配置说明: +1. 使用SileroVAD模型进行语音活动检测 +2. 本地推理,无需网络连接 +3. 需要下载模型文件到models/snakers4_silero-vad目录 +4. 可配置参数: + - threshold: 0.5(语音检测阈值) + - min_silence_duration_ms: 700(最小静音持续时间,单位毫秒) +5. 如果说话停顿比较长,可以适当增加min_silence_duration_ms的值' WHERE `id` = 'VAD_SileroVAD'; diff --git a/main/manager-api/src/main/resources/db/changelog/202504301341.sql b/main/manager-api/src/main/resources/db/changelog/202504301341.sql new file mode 100644 index 00000000..a88ee783 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202504301341.sql @@ -0,0 +1,85 @@ +update `ai_model_provider` set `fields` = +'[{"key": "api_url","label": "API地址","type": "string"},{"key": "voice","label": "音色","type": "string"},{"key": "output_dir","label": "输出目录","type": "string"},{"key": "authorization","label": "授权","type": "string"},{"key": "appid","label": "应用ID","type": "string"},{"key": "access_token","label": "访问令牌","type": "string"},{"key": "cluster","label": "集群","type": "string"},{"key": "speed_ratio","label": "语速","type": "number"},{"key": "volume_ratio","label": "音量","type": "number"},{"key": "pitch_ratio","label": "音高","type": "number"}]' +where `id` = 'SYSTEM_TTS_doubao'; + +-- 添加阿里云ASR供应器 +delete from `ai_model_provider` where `id` = 'SYSTEM_ASR_AliyunASR'; +INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES +('SYSTEM_ASR_AliyunASR', 'ASR', 'aliyun', '阿里云语音识别', '[{"key":"appkey","label":"应用AppKey","type":"string"},{"key":"token","label":"临时Token","type":"string"},{"key":"access_key_id","label":"AccessKey ID","type":"string"},{"key":"access_key_secret","label":"AccessKey Secret","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 5, 1, NOW(), 1, NOW()); + +-- 添加阿里云ASR模型配置 +delete from `ai_model_config` where `id` = 'ASR_AliyunASR'; +INSERT INTO `ai_model_config` VALUES ('ASR_AliyunASR', 'ASR', 'AliyunASR', '阿里云语音识别', 0, 1, '{\"type\": \"aliyun\", \"appkey\": \"\", \"token\": \"\", \"access_key_id\": \"\", \"access_key_secret\": \"\", \"output_dir\": \"tmp/\"}', NULL, NULL, 6, NULL, NULL, NULL, NULL); + +-- 更新阿里云ASR模型配置的说明文档 +UPDATE `ai_model_config` SET +`doc_link` = 'https://nls-portal.console.aliyun.com/', +`remark` = '阿里云ASR配置说明: +1. 访问 https://nls-portal.console.aliyun.com/ 开通服务 +2. 访问 https://nls-portal.console.aliyun.com/applist 获取appkey +3. 访问 https://nls-portal.console.aliyun.com/overview 获取token +4. 获取access_key_id和access_key_secret +5. 填入配置文件中' WHERE `id` = 'ASR_AliyunASR'; + +-- 插入固件类型字典类型 +delete from `sys_dict_type` where `id` = 101; +INSERT INTO `sys_dict_type` (`id`, `dict_type`, `dict_name`, `remark`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES +(101, 'FIRMWARE_TYPE', '固件类型', '固件类型字典', 0, 1, NOW(), 1, NOW()); + +-- 插入固件类型字典数据 +delete from `sys_dict_data` where `dict_type_id` = 101; +INSERT INTO `sys_dict_data` (`id`, `dict_type_id`, `dict_label`, `dict_value`, `remark`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES +(101001, 101, '面包板新版接线(WiFi)', 'bread-compact-wifi', '面包板新版接线(WiFi)', 1, 1, NOW(), 1, NOW()), +(101002, 101, '面包板新版接线(WiFi)+ LCD', 'bread-compact-wifi-lcd', '面包板新版接线(WiFi)+ LCD', 2, 1, NOW(), 1, NOW()), +(101003, 101, '面包板新版接线(ML307 AT)', 'bread-compact-ml307', '面包板新版接线(ML307 AT)', 3, 1, NOW(), 1, NOW()), +(101004, 101, '面包板(WiFi) ESP32 DevKit', 'bread-compact-esp32', '面包板(WiFi) ESP32 DevKit', 4, 1, NOW(), 1, NOW()), +(101005, 101, '面包板(WiFi+ LCD) ESP32 DevKit', 'bread-compact-esp32-lcd', '面包板(WiFi+ LCD) ESP32 DevKit', 5, 1, NOW(), 1, NOW()), +(101006, 101, 'DFRobot 行空板 k10', 'df-k10', 'DFRobot 行空板 k10', 6, 1, NOW(), 1, NOW()), +(101007, 101, 'ESP32 CGC', 'esp32-cgc', 'ESP32 CGC', 7, 1, NOW(), 1, NOW()), +(101008, 101, 'ESP BOX 3', 'esp-box-3', 'ESP BOX 3', 8, 1, NOW(), 1, NOW()), +(101009, 101, 'ESP BOX', 'esp-box', 'ESP BOX', 9, 1, NOW(), 1, NOW()), +(101010, 101, 'ESP BOX Lite', 'esp-box-lite', 'ESP BOX Lite', 10, 1, NOW(), 1, NOW()), +(101011, 101, 'Kevin Box 1', 'kevin-box-1', 'Kevin Box 1', 11, 1, NOW(), 1, NOW()), +(101012, 101, 'Kevin Box 2', 'kevin-box-2', 'Kevin Box 2', 12, 1, NOW(), 1, NOW()), +(101013, 101, 'Kevin C3', 'kevin-c3', 'Kevin C3', 13, 1, NOW(), 1, NOW()), +(101014, 101, 'Kevin SP V3开发板', 'kevin-sp-v3-dev', 'Kevin SP V3开发板', 14, 1, NOW(), 1, NOW()), +(101015, 101, 'Kevin SP V4开发板', 'kevin-sp-v4-dev', 'Kevin SP V4开发板', 15, 1, NOW(), 1, NOW()), +(101016, 101, '鱼鹰科技3.13LCD开发板', 'kevin-yuying-313lcd', '鱼鹰科技3.13LCD开发板', 16, 1, NOW(), 1, NOW()), +(101017, 101, '立创·实战派ESP32-S3开发板', 'lichuang-dev', '立创·实战派ESP32-S3开发板', 17, 1, NOW(), 1, NOW()), +(101018, 101, '立创·实战派ESP32-C3开发板', 'lichuang-c3-dev', '立创·实战派ESP32-C3开发板', 18, 1, NOW(), 1, NOW()), +(101019, 101, '神奇按钮 Magiclick_2.4', 'magiclick-2p4', '神奇按钮 Magiclick_2.4', 19, 1, NOW(), 1, NOW()), +(101020, 101, '神奇按钮 Magiclick_2.5', 'magiclick-2p5', '神奇按钮 Magiclick_2.5', 20, 1, NOW(), 1, NOW()), +(101021, 101, '神奇按钮 Magiclick_C3', 'magiclick-c3', '神奇按钮 Magiclick_C3', 21, 1, NOW(), 1, NOW()), +(101022, 101, '神奇按钮 Magiclick_C3_v2', 'magiclick-c3-v2', '神奇按钮 Magiclick_C3_v2', 22, 1, NOW(), 1, NOW()), +(101023, 101, 'M5Stack CoreS3', 'm5stack-core-s3', 'M5Stack CoreS3', 23, 1, NOW(), 1, NOW()), +(101024, 101, 'AtomS3 + Echo Base', 'atoms3-echo-base', 'AtomS3 + Echo Base', 24, 1, NOW(), 1, NOW()), +(101025, 101, 'AtomS3R + Echo Base', 'atoms3r-echo-base', 'AtomS3R + Echo Base', 25, 1, NOW(), 1, NOW()), +(101026, 101, 'AtomS3R CAM/M12 + Echo Base', 'atoms3r-cam-m12-echo-base', 'AtomS3R CAM/M12 + Echo Base', 26, 1, NOW(), 1, NOW()), +(101027, 101, 'AtomMatrix + Echo Base', 'atommatrix-echo-base', 'AtomMatrix + Echo Base', 27, 1, NOW(), 1, NOW()), +(101028, 101, '虾哥 Mini C3', 'xmini-c3', '虾哥 Mini C3', 28, 1, NOW(), 1, NOW()), +(101029, 101, 'ESP32S3_KORVO2_V3开发板', 'esp32s3-korvo2-v3', 'ESP32S3_KORVO2_V3开发板', 29, 1, NOW(), 1, NOW()), +(101030, 101, 'ESP-SparkBot开发板', 'esp-sparkbot', 'ESP-SparkBot开发板', 30, 1, NOW(), 1, NOW()), +(101031, 101, 'ESP-Spot-S3', 'esp-spot-s3', 'ESP-Spot-S3', 31, 1, NOW(), 1, NOW()), +(101032, 101, 'Waveshare ESP32-S3-Touch-AMOLED-1.8', 'esp32-s3-touch-amoled-1.8', 'Waveshare ESP32-S3-Touch-AMOLED-1.8', 32, 1, NOW(), 1, NOW()), +(101033, 101, 'Waveshare ESP32-S3-Touch-LCD-1.85C', 'esp32-s3-touch-lcd-1.85c', 'Waveshare ESP32-S3-Touch-LCD-1.85C', 33, 1, NOW(), 1, NOW()), +(101034, 101, 'Waveshare ESP32-S3-Touch-LCD-1.85', 'esp32-s3-touch-lcd-1.85', 'Waveshare ESP32-S3-Touch-LCD-1.85', 34, 1, NOW(), 1, NOW()), +(101035, 101, 'Waveshare ESP32-S3-Touch-LCD-1.46', 'esp32-s3-touch-lcd-1.46', 'Waveshare ESP32-S3-Touch-LCD-1.46', 35, 1, NOW(), 1, NOW()), +(101036, 101, 'Waveshare ESP32-S3-Touch-LCD-3.5', 'esp32-s3-touch-lcd-3.5', 'Waveshare ESP32-S3-Touch-LCD-3.5', 36, 1, NOW(), 1, NOW()), +(101037, 101, '土豆子', 'tudouzi', '土豆子', 37, 1, NOW(), 1, NOW()), +(101038, 101, 'LILYGO T-Circle-S3', 'lilygo-t-circle-s3', 'LILYGO T-Circle-S3', 38, 1, NOW(), 1, NOW()), +(101039, 101, 'LILYGO T-CameraPlus-S3', 'lilygo-t-cameraplus-s3', 'LILYGO T-CameraPlus-S3', 39, 1, NOW(), 1, NOW()), +(101040, 101, 'Movecall Moji 小智AI衍生版', 'movecall-moji-esp32s3', 'Movecall Moji 小智AI衍生版', 40, 1, NOW(), 1, NOW()), +(101041, 101, 'Movecall CuiCan 璀璨·AI吊坠', 'movecall-cuican-esp32s3', 'Movecall CuiCan 璀璨·AI吊坠', 41, 1, NOW(), 1, NOW()), +(101042, 101, '正点原子DNESP32S3开发板', 'atk-dnesp32s3', '正点原子DNESP32S3开发板', 42, 1, NOW(), 1, NOW()), +(101043, 101, '正点原子DNESP32S3-BOX', 'atk-dnesp32s3-box', '正点原子DNESP32S3-BOX', 43, 1, NOW(), 1, NOW()), +(101044, 101, '嘟嘟开发板CHATX(wifi)', 'du-chatx', '嘟嘟开发板CHATX(wifi)', 44, 1, NOW(), 1, NOW()), +(101045, 101, '太极小派esp32s3', 'taiji-pi-s3', '太极小派esp32s3', 45, 1, NOW(), 1, NOW()), +(101046, 101, '无名科技星智0.85(WIFI)', 'xingzhi-cube-0.85tft-wifi', '无名科技星智0.85(WIFI)', 46, 1, NOW(), 1, NOW()), +(101047, 101, '无名科技星智0.85(ML307)', 'xingzhi-cube-0.85tft-ml307', '无名科技星智0.85(ML307)', 47, 1, NOW(), 1, NOW()), +(101048, 101, '无名科技星智0.96(WIFI)', 'xingzhi-cube-0.96oled-wifi', '无名科技星智0.96(WIFI)', 48, 1, NOW(), 1, NOW()), +(101049, 101, '无名科技星智0.96(ML307)', 'xingzhi-cube-0.96oled-ml307', '无名科技星智0.96(ML307)', 49, 1, NOW(), 1, NOW()), +(101050, 101, '无名科技星智1.54(WIFI)', 'xingzhi-cube-1.54tft-wifi', '无名科技星智1.54(WIFI)', 50, 1, NOW(), 1, NOW()), +(101051, 101, '无名科技星智1.54(ML307)', 'xingzhi-cube-1.54tft-ml307', '无名科技星智1.54(ML307)', 51, 1, NOW(), 1, NOW()), +(101052, 101, 'SenseCAP Watcher', 'sensecap-watcher', 'SenseCAP Watcher', 52, 1, NOW(), 1, NOW()), +(101053, 101, '四博智联AI陪伴盒子', 'doit-s3-aibox', '四博智联AI陪伴盒子', 53, 1, NOW(), 1, NOW()), +(101054, 101, '元控·青春', 'mixgo-nova', '元控·青春', 54, 1, NOW(), 1, NOW()); diff --git a/main/manager-api/src/main/resources/db/changelog/202505022134.sql b/main/manager-api/src/main/resources/db/changelog/202505022134.sql new file mode 100644 index 00000000..ccbaaaee --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505022134.sql @@ -0,0 +1,27 @@ +-- 初始化智能体聊天记录 +DROP TABLE IF EXISTS ai_chat_history; +DROP TABLE IF EXISTS ai_chat_message; +DROP TABLE IF EXISTS ai_agent_chat_history; +CREATE TABLE ai_agent_chat_history +( + id BIGINT AUTO_INCREMENT COMMENT '主键ID' PRIMARY KEY, + mac_address VARCHAR(50) COMMENT 'MAC地址', + agent_id VARCHAR(32) COMMENT '智能体id', + session_id VARCHAR(50) COMMENT '会话ID', + chat_type TINYINT(3) COMMENT '消息类型: 1-用户, 2-智能体', + content VARCHAR(1024) COMMENT '聊天内容', + audio_id VARCHAR(32) COMMENT '音频ID', + created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL COMMENT '创建时间', + updated_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', + INDEX idx_ai_agent_chat_history_mac (mac_address), + INDEX idx_ai_agent_chat_history_session_id (session_id), + INDEX idx_ai_agent_chat_history_agent_id (agent_id), + INDEX idx_ai_agent_chat_history_agent_session_created (agent_id, session_id, created_at) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '智能体聊天记录表'; + +DROP TABLE IF EXISTS ai_agent_chat_audio; +CREATE TABLE ai_agent_chat_audio +( + id VARCHAR(32) COMMENT '主键ID' PRIMARY KEY, + audio LONGBLOB COMMENT '音频opus数据' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '智能体聊天音频数据表'; \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/202505081146.sql b/main/manager-api/src/main/resources/db/changelog/202505081146.sql new file mode 100644 index 00000000..fe7582f3 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505081146.sql @@ -0,0 +1,43 @@ +-- 添加百度ASR模型配置 +delete from `ai_model_config` where `id` = 'ASR_BaiduASR'; +INSERT INTO `ai_model_config` VALUES ('ASR_BaiduASR', 'ASR', 'BaiduASR', '百度语音识别', 0, 1, '{\"type\": \"baidu\", \"app_id\": \"\", \"api_key\": \"\", \"secret_key\": \"\", \"dev_pid\": 1537, \"output_dir\": \"tmp/\"}', NULL, NULL, 7, NULL, NULL, NULL, NULL); + + +-- 添加百度ASR供应器 +delete from `ai_model_provider` where `id` = 'SYSTEM_ASR_BaiduASR'; +INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES +('SYSTEM_ASR_BaiduASR', 'ASR', 'baidu', '百度语音识别', '[{"key":"app_id","label":"应用AppID","type":"string"},{"key":"api_key","label":"API Key","type":"string"},{"key":"secret_key","label":"Secret Key","type":"string"},{"key":"dev_pid","label":"语言参数","type":"number"},{"key":"output_dir","label":"输出目录","type":"string"}]', 7, 1, NOW(), 1, NOW()); + + +-- 更新百度ASR配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://console.bce.baidu.com/ai-engine/old/#/ai/speech/app/list', +`remark` = '百度ASR配置说明: +1. 访问 https://console.bce.baidu.com/ai-engine/old/#/ai/speech/app/list +2. 创建新应用 +3. 获取AppID、API Key和Secret Key +4. 填入配置文件中 +查看资源额度:https://console.bce.baidu.com/ai-engine/old/#/ai/speech/overview/resource/list +语言参数说明:https://ai.baidu.com/ai-doc/SPEECH/0lbxfnc9b +' WHERE `id` = 'ASR_BaiduASR'; + +-- 更新豆包供应器字段 +update `ai_model_provider` set `fields` = +'[{"key":"appid","label":"应用ID","type":"string"},{"key":"access_token","label":"访问令牌","type":"string"},{"key":"cluster","label":"集群","type":"string"},{"key":"boosting_table_name","label":"热词文件名称","type":"string"},{"key":"correct_table_name","label":"替换词文件名称","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]' +where `id` = 'SYSTEM_ASR_DoubaoASR'; + +-- 更新豆包ASR配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://console.volcengine.com/speech/app', +`remark` = '豆包ASR配置说明: +1. 需要在火山引擎控制台创建应用并获取appid和access_token +2. 支持中文语音识别 +3. 需要网络连接 +4. 输出文件保存在tmp/目录 +申请步骤: +1. 访问 https://console.volcengine.com/speech/app +2. 创建新应用 +3. 获取appid和access_token +4. 填入配置文件中 +如需设置热词,请参考:https://www.volcengine.com/docs/6561/155738 +' WHERE `id` = 'ASR_DoubaoASR'; \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/202505091409.sql b/main/manager-api/src/main/resources/db/changelog/202505091409.sql new file mode 100644 index 00000000..76ea8ae0 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505091409.sql @@ -0,0 +1,6 @@ +-- 更新intent_llmM供应器 +update `ai_model_provider` set fields = '[{"key":"llm","label":"LLM模型","type":"string"},{"key":"functions","label":"函数列表","type":"dict","dict_name":"functions"}]' where id = 'SYSTEM_Intent_intent_llm'; +-- 更新ChatGLMLLM的意图识别配置 +update `ai_model_config` set config_json = '{\"type\": \"intent_llm\", \"llm\": \"LLM_ChatGLMLLM\", \"functions\": \"get_weather;get_news_from_newsnow;play_music\"}' where id = 'Intent_intent_llm'; +-- 更新函数调用意图识别配置 +UPDATE `ai_model_config` SET config_json = REPLACE(config_json, ';get_news;', ';get_news_from_newsnow;') WHERE id = 'Intent_function_call'; diff --git a/main/manager-api/src/main/resources/db/changelog/202505091555.sql b/main/manager-api/src/main/resources/db/changelog/202505091555.sql new file mode 100644 index 00000000..b7ad91d4 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505091555.sql @@ -0,0 +1,25 @@ +-- 更新模型供应器表 +UPDATE `ai_model_provider` SET fields = '[{"key": "host", "type": "string", "label": "服务地址"}, {"key": "port", "type": "number", "label": "端口号"}, {"key": "type", "type": "string", "label": "服务类型"}, {"key": "is_ssl", "type": "boolean", "label": "是否使用SSL"}, {"key": "api_key", "type": "string", "label": "API密钥"}, {"key": "output_dir", "type": "string", "label": "输出目录"}]' WHERE id = 'SYSTEM_ASR_FunASRServer'; + +-- 更新模型配置表 +UPDATE `ai_model_config` SET +config_json = '{"host": "127.0.0.1", "port": 10096, "type": "fun_server", "is_ssl": true, "api_key": "none", "output_dir": "tmp/"}', +`doc_link` = 'https://github.com/modelscope/FunASR/blob/main/runtime/docs/SDK_advanced_guide_online_zh.md', +`remark` = '独立部署FunASR,使用FunASR的API服务,只需要五句话 +第一句:mkdir -p ./funasr-runtime-resources/models +第二句:sudo docker run -p 10096:10095 -it --privileged=true -v $PWD/funasr-runtime-resources/models:/workspace/models registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-online-cpu-0.1.12 +上一句话执行后会进入到容器,继续第三句:cd FunASR/runtime +不要退出容器,继续在容器中执行第四句:nohup bash run_server_2pass.sh --download-model-dir /workspace/models --vad-dir damo/speech_fsmn_vad_zh-cn-16k-common-onnx --model-dir damo/speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-onnx --online-model-dir damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online-onnx --punc-dir damo/punc_ct-transformer_zh-cn-common-vad_realtime-vocab272727-onnx --lm-dir damo/speech_ngram_lm_zh-cn-ai-wesp-fst --itn-dir thuduj12/fst_itn_zh --hotword /workspace/models/hotwords.txt > log.txt 2>&1 & +上一句话执行后会进入到容器,继续第五句:tail -f log.txt +第五句话执行完后,会看到模型下载日志,下载完后就可以连接使用了 +以上是使用CPU推理,如果有GPU,详细参考:https://github.com/modelscope/FunASR/blob/main/runtime/docs/SDK_advanced_guide_online_zh.md' WHERE `id` = 'ASR_FunASRServer'; + +-- FishSpeech配置说明 +UPDATE `ai_model_config` SET +`doc_link` = 'https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/docs/fish-speech-integration.md', +`remark` = 'FishSpeech配置说明: +1. 需要本地部署FishSpeech服务 +2. 支持自定义音色 +3. 本地推理,无需网络连接 +4. 输出文件保存在tmp/目录 +5. 可参照教程https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/docs/fish-speech-integration.md' WHERE `id` = 'TTS_FishSpeech'; diff --git a/main/manager-api/src/main/resources/db/changelog/202505111914.sql b/main/manager-api/src/main/resources/db/changelog/202505111914.sql new file mode 100644 index 00000000..c67f3477 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505111914.sql @@ -0,0 +1,6 @@ +-- 添加聊天记录配置字段 +ALTER TABLE `ai_agent` +ADD COLUMN `chat_history_conf` tinyint NOT NULL DEFAULT 0 COMMENT '聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)' AFTER `system_prompt`; + +ALTER TABLE `ai_agent_template` +ADD COLUMN `chat_history_conf` tinyint NOT NULL DEFAULT 0 COMMENT '聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)' AFTER `system_prompt`; \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/202505122348.sql b/main/manager-api/src/main/resources/db/changelog/202505122348.sql new file mode 100644 index 00000000..05bbeb84 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505122348.sql @@ -0,0 +1,6 @@ +-- 添加总结记忆字段 +ALTER TABLE `ai_agent` +ADD COLUMN `summary_memory` text COMMENT '总结记忆' AFTER `system_prompt`; + +ALTER TABLE `ai_agent_template` +ADD COLUMN `summary_memory` text COMMENT '总结记忆' AFTER `system_prompt`; diff --git a/main/manager-api/src/main/resources/db/changelog/202505142037.sql b/main/manager-api/src/main/resources/db/changelog/202505142037.sql new file mode 100644 index 00000000..37a49013 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505142037.sql @@ -0,0 +1,7 @@ +update ai_agent_template set system_prompt = replace(system_prompt, '我是', '你是'); + +delete from sys_params where id in (500,501,402); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (500, 'end_prompt.enable', 'true', 'boolean', 1, '是否开启结束语'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (501, 'end_prompt.prompt', '请你以“时间过得真快”未来头,用富有感情、依依不舍的话来结束这场对话吧!', 'string', 1, '结束提示词'); + +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (402, 'plugins.get_weather.api_host', 'mj7p3y7naa.re.qweatherapi.com', 'string', 1, '开发者apihost'); \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/202505182234.sql b/main/manager-api/src/main/resources/db/changelog/202505182234.sql new file mode 100644 index 00000000..3873e5d4 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505182234.sql @@ -0,0 +1,65 @@ +-- 添加手机短信注册功能的需要的参数 +delete from sys_params where id in (108, 109, 110, 111, 112, 113, 114, 115); +delete from sys_params where id in (610, 611, 612, 613); +INSERT INTO sys_params +(id, param_code, param_value, value_type, param_type, remark, creator, create_date, updater, update_date) + VALUES +(108, 'server.name', 'xiaozhi-esp32-server', 'string', 1, '系统名称', NULL, NULL, NULL, NULL), +(109, 'server.beian_icp_num', 'null', 'string', 1, 'icp备案号,填写null则不设置', NULL, NULL, NULL, NULL), +(110, 'server.beian_ga_num', 'null', 'string', 1, '公安备案号,填写null则不设置', NULL, NULL, NULL, NULL), +(111, 'server.enable_mobile_register', 'false', 'boolean', 1, '是否开启手机注册', NULL, NULL, NULL, NULL), +(112, 'server.sms_max_send_count', '10', 'number', 1, '单号码单日最大短信发送条数', NULL, NULL, NULL, NULL), +(610, 'aliyun.sms.access_key_id', '', 'string', 1, '阿里云平台access_key', NULL, NULL, NULL, NULL), +(611, 'aliyun.sms.access_key_secret', '', 'string', 1, '阿里云平台access_key_secret', NULL, NULL, NULL, NULL), +(612, 'aliyun.sms.sign_name', '', 'string', 1, '阿里云短信签名', NULL, NULL, NULL, NULL), +(613, 'aliyun.sms.sms_code_template_code', '', 'string', 1, '阿里云短信模板', NULL, NULL, NULL, NULL); + +update sys_params set remark = '是否允许管理员以外的人注册' where param_code = 'server.allow_user_register'; + +-- 增加手机区域字典 +-- 插入固件类型字典类型 +delete from `sys_dict_type` where `id` = 102; +INSERT INTO `sys_dict_type` (`id`, `dict_type`, `dict_name`, `remark`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES +(102, 'MOBILE_AREA', '手机区域', '手机区域字典', 0, 1, NOW(), 1, NOW()); + +-- 插入固件类型字典数据 +delete from `sys_dict_data` where `dict_type_id` = 102; +INSERT INTO `sys_dict_data` (`id`, `dict_type_id`, `dict_label`, `dict_value`, `remark`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES +(102001, 102, '中国大陆', '+86', '中国大陆', 1, 1, NOW(), 1, NOW()), +(102002, 102, '中国香港', '+852', '中国香港', 2, 1, NOW(), 1, NOW()), +(102003, 102, '中国澳门', '+853', '中国澳门', 3, 1, NOW(), 1, NOW()), +(102004, 102, '中国台湾', '+886', '中国台湾', 4, 1, NOW(), 1, NOW()), +(102005, 102, '美国/加拿大', '+1', '美国/加拿大', 5, 1, NOW(), 1, NOW()), +(102006, 102, '英国', '+44', '英国', 6, 1, NOW(), 1, NOW()), +(102007, 102, '法国', '+33', '法国', 7, 1, NOW(), 1, NOW()), +(102008, 102, '意大利', '+39', '意大利', 8, 1, NOW(), 1, NOW()), +(102009, 102, '德国', '+49', '德国', 9, 1, NOW(), 1, NOW()), +(102010, 102, '波兰', '+48', '波兰', 10, 1, NOW(), 1, NOW()), +(102011, 102, '瑞士', '+41', '瑞士', 11, 1, NOW(), 1, NOW()), +(102012, 102, '西班牙', '+34', '西班牙', 12, 1, NOW(), 1, NOW()), +(102013, 102, '丹麦', '+45', '丹麦', 13, 1, NOW(), 1, NOW()), +(102014, 102, '马来西亚', '+60', '马来西亚', 14, 1, NOW(), 1, NOW()), +(102015, 102, '澳大利亚', '+61', '澳大利亚', 15, 1, NOW(), 1, NOW()), +(102016, 102, '印度尼西亚', '+62', '印度尼西亚', 16, 1, NOW(), 1, NOW()), +(102017, 102, '菲律宾', '+63', '菲律宾', 17, 1, NOW(), 1, NOW()), +(102018, 102, '新西兰', '+64', '新西兰', 18, 1, NOW(), 1, NOW()), +(102019, 102, '新加坡', '+65', '新加坡', 19, 1, NOW(), 1, NOW()), +(102020, 102, '泰国', '+66', '泰国', 20, 1, NOW(), 1, NOW()), +(102021, 102, '日本', '+81', '日本', 21, 1, NOW(), 1, NOW()), +(102022, 102, '韩国', '+82', '韩国', 22, 1, NOW(), 1, NOW()), +(102023, 102, '越南', '+84', '越南', 23, 1, NOW(), 1, NOW()), +(102024, 102, '印度', '+91', '印度', 24, 1, NOW(), 1, NOW()), +(102025, 102, '巴基斯坦', '+92', '巴基斯坦', 25, 1, NOW(), 1, NOW()), +(102026, 102, '尼日利亚', '+234', '尼日利亚', 26, 1, NOW(), 1, NOW()), +(102027, 102, '孟加拉国', '+880', '孟加拉国', 27, 1, NOW(), 1, NOW()), +(102028, 102, '沙特阿拉伯', '+966', '沙特阿拉伯', 28, 1, NOW(), 1, NOW()), +(102029, 102, '阿联酋', '+971', '阿联酋', 29, 1, NOW(), 1, NOW()), +(102030, 102, '巴西', '+55', '巴西', 30, 1, NOW(), 1, NOW()), +(102031, 102, '墨西哥', '+52', '墨西哥', 31, 1, NOW(), 1, NOW()), +(102032, 102, '智利', '+56', '智利', 32, 1, NOW(), 1, NOW()), +(102033, 102, '阿根廷', '+54', '阿根廷', 33, 1, NOW(), 1, NOW()), +(102034, 102, '埃及', '+20', '埃及', 34, 1, NOW(), 1, NOW()), +(102035, 102, '南非', '+27', '南非', 35, 1, NOW(), 1, NOW()), +(102036, 102, '肯尼亚', '+254', '肯尼亚', 36, 1, NOW(), 1, NOW()), +(102037, 102, '坦桑尼亚', '+255', '坦桑尼亚', 37, 1, NOW(), 1, NOW()), +(102038, 102, '哈萨克斯坦', '+7', '哈萨克斯坦', 38, 1, NOW(), 1, NOW()); diff --git a/main/manager-api/src/main/resources/db/changelog/202505201744.sql b/main/manager-api/src/main/resources/db/changelog/202505201744.sql new file mode 100644 index 00000000..7b31b1cc --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505201744.sql @@ -0,0 +1,3 @@ +-- 更新ai_model_provider的fields字段,将type为dict的改为string +update ai_model_provider set fields = replace(fields, '"type": "dict"', '"type": "string"') where id not in ('SYSTEM_LLM_fastgpt', 'SYSTEM_TTS_custom'); +update ai_model_provider set fields = replace(fields, '"type":"dict"', '"type": "string"') where id not in ('SYSTEM_LLM_fastgpt', 'SYSTEM_TTS_custom'); \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/2025_tts_voive.sql b/main/manager-api/src/main/resources/db/changelog/2025_tts_voive.sql deleted file mode 100644 index bea47d7a..00000000 --- a/main/manager-api/src/main/resources/db/changelog/2025_tts_voive.sql +++ /dev/null @@ -1,44 +0,0 @@ --- 本文件用于初始化音色数据,无需手动执行,在项目启动时会自动执行 --- ---------------------------------------------------- - --- 初始化音色数据 -DELETE FROM `ai_tts_voice`; - --- EdgeTTS音色 -INSERT INTO `ai_tts_voice` VALUES ('TTS_EdgeTTS0001', 'TTS_EdgeTTS', 'EdgeTTS女声', 'zh-CN-XiaoxiaoNeural', '中文', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/EdgeTTS.mp3', NULL, 1, NULL, NULL, NULL, NULL); - --- DoubaoTTS音色 -INSERT INTO `ai_tts_voice` VALUES ('TTS_DoubaoTTS0001', 'TTS_DoubaoTTS', '通用女声', 'BV001_streaming', '中文', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV001.mp3', NULL, 3, NULL, NULL, NULL, NULL); -INSERT INTO `ai_tts_voice` VALUES ('TTS_DoubaoTTS0002', 'TTS_DoubaoTTS', '通用男声', 'BV002_streaming', '中文', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV002.mp3', NULL, 2, NULL, NULL, NULL, NULL); -INSERT INTO `ai_tts_voice` VALUES ('TTS_DoubaoTTS0003', 'TTS_DoubaoTTS', '阳光男生', 'BV056_streaming', '中文', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV056.mp3', NULL, 4, NULL, NULL, NULL, NULL); -INSERT INTO `ai_tts_voice` VALUES ('TTS_DoubaoTTS0004', 'TTS_DoubaoTTS', '奶气萌娃', 'BV051_streaming', '中文', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV051.mp3', NULL, 5, NULL, NULL, NULL, NULL); - --- CosyVoiceSiliconflow音色 -INSERT INTO `ai_tts_voice` VALUES ('TTS_CosyVoiceSiliconflow0001', 'TTS_CosyVoiceSiliconflow', 'CosyVoice男声', 'alex', '中文', 'https://example.com/cosyvoice/alex.mp3', NULL, 6, NULL, NULL, NULL, NULL); - --- CozeCnTTS音色 -INSERT INTO `ai_tts_voice` VALUES ('TTS_CozeCnTTS0001', 'TTS_CozeCnTTS', 'CozeCn音色', '7426720361733046281', '中文', 'https://example.com/cozecn/voice.mp3', NULL, 7, NULL, NULL, NULL, NULL); - --- MinimaxTTS音色 -INSERT INTO `ai_tts_voice` VALUES ('TTS_MinimaxTTS0001', 'TTS_MinimaxTTS', 'Minimax少女音', 'female-shaonv', '中文', 'https://example.com/minimax/female-shaonv.mp3', NULL, 8, NULL, NULL, NULL, NULL); - --- AliyunTTS音色 -INSERT INTO `ai_tts_voice` VALUES ('TTS_AliyunTTS0001', 'TTS_AliyunTTS', '阿里云小云', 'xiaoyun', '中文', 'https://example.com/aliyun/xiaoyun.mp3', NULL, 9, NULL, NULL, NULL, NULL); - --- TTS302AI音色 -INSERT INTO `ai_tts_voice` VALUES ('TTS_TTS302AI0001', 'TTS_TTS302AI', '湾湾小何', 'zh_female_wanwanxiaohe_moon_bigtts', '中文', 'https://example.com/302ai/wanwanxiaohe.mp3', NULL, 10, NULL, NULL, NULL, NULL); - --- GizwitsTTS音色 -INSERT INTO `ai_tts_voice` VALUES ('TTS_GizwitsTTS0001', 'TTS_GizwitsTTS', '机智云湾湾', 'zh_female_wanwanxiaohe_moon_bigtts', '中文', 'https://example.com/gizwits/wanwanxiaohe.mp3', NULL, 11, NULL, NULL, NULL, NULL); - --- ACGNTTS音色 -INSERT INTO `ai_tts_voice` VALUES ('TTS_ACGNTTS0001', 'TTS_ACGNTTS', 'ACG音色', '1695', '中文', 'https://example.com/acgn/voice.mp3', NULL, 12, NULL, NULL, NULL, NULL); - --- OpenAITTS音色 -INSERT INTO `ai_tts_voice` VALUES ('TTS_OpenAITTS0001', 'TTS_OpenAITTS', 'OpenAI男声', 'onyx', '中文', 'https://example.com/openai/onyx.mp3', NULL, 13, NULL, NULL, NULL, NULL); - --- 其他音色 -INSERT INTO `ai_tts_voice` VALUES ('TTS_FishSpeech0000', 'TTS_FishSpeech', '', '', '中文', '', NULL, 8, NULL, NULL, NULL, NULL); -INSERT INTO `ai_tts_voice` VALUES ('TTS_GPT_SOVITS_V20000', 'TTS_GPT_SOVITS_V2', '', '', '中文', '', NULL, 8, NULL, NULL, NULL, NULL); -INSERT INTO `ai_tts_voice` VALUES ('TTS_GPT_SOVITS_V30000', 'TTS_GPT_SOVITS_V3', '', '', '中文', '', NULL, 8, NULL, NULL, NULL, NULL); -INSERT INTO `ai_tts_voice` VALUES ('TTS_CustomTTS0000', 'TTS_CustomTTS', '', '', '中文', '', NULL, 8, NULL, NULL, NULL, NULL); \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index 49b958db..22cef133 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -16,31 +16,143 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202503141346.sql - - changeSet: - id: 2025_tts_voive - author: John - changes: - - sqlFile: - encoding: utf8 - path: classpath:db/changelog/2025_tts_voive.sql - - changeSet: - id: 202504061512 - author: John - changes: - - sqlFile: - encoding: utf8 - path: classpath:db/changelog/202504061512.sql - - changeSet: - id: 202504082210 - author: John - changes: - - sqlFile: - encoding: utf8 - path: classpath:db/changelog/202504082210.sql - changeSet: id: 202504082211 author: John changes: - sqlFile: encoding: utf8 - path: classpath:db/changelog/202504082211.sql \ No newline at end of file + path: classpath:db/changelog/202504082211.sql + - changeSet: + id: 202504092335 + author: John + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202504092335.sql + - changeSet: + id: 202504112044 + author: John + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202504112044.sql + - changeSet: + id: 202504112058 + author: John + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202504112058.sql + - changeSet: + id: 202504151206 + author: John + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202504151206.sql + - changeSet: + id: 202504181536 + author: John + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202504181536.sql + - changeSet: + id: 202504221135 + author: John + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202504221135.sql + - changeSet: + id: 202504221555 + author: John + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202504221555.sql + - changeSet: + id: 202504251422 + author: jiangkunyin + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202504251422.sql + - changeSet: + id: 202504291043 + author: jiangkunyin + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202504291043.sql + - changeSet: + id: 202504301341 + author: Goody + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202504301341.sql + - changeSet: + id: 202505022134 + author: Goody + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505022134.sql + - changeSet: + id: 202505081146 + author: hrz + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505081146.sql + - changeSet: + id: 202505091409 + author: hrz + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505091409.sql + - changeSet: + id: 202505091555 + author: whosmyqueen + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505091555.sql + - changeSet: + id: 202505111914 + author: hrz + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505111914.sql + - changeSet: + id: 202505122348 + author: ljwwd2 + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505122348.sql + - changeSet: + id: 202505142037 + author: hrz + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505142037.sql + - changeSet: + id: 202505182234 + author: amen + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505182234.sql + - changeSet: + id: 202505201744 + author: hrz + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505201744.sql \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/messages.properties b/main/manager-api/src/main/resources/i18n/messages.properties index 91e6a285..8d04c5a0 100644 --- a/main/manager-api/src/main/resources/i18n/messages.properties +++ b/main/manager-api/src/main/resources/i18n/messages.properties @@ -9,6 +9,7 @@ 10005=\u8D26\u53F7\u5DF2\u88AB\u505C\u7528 10006=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A 10007=\u9A8C\u8BC1\u7801\u4E0D\u6B63\u786E +10008=\u624B\u673A\u53F7\u7801\u4E0D\u53EF\u4EE5\u4E3A\u7A7A 10009=\u539F\u5BC6\u7801\u4E0D\u6B63\u786E 10010=\u8D26\u53F7\u6216\u5BC6\u7801\u4E0D\u6B63\u786E,\u60A8\u8FD8\u6709\u53EF\u4EE5\u5C1D\u8BD5{0}\u6B21 10011=\u4E0A\u7EA7\u90E8\u95E8\u9009\u62E9\u9519\u8BEF @@ -34,3 +35,13 @@ 10031=\u5F31\u5BC6\u7801 10032=\u5220\u9664\u672C\u6570\u636E\u5F02\u5E38 10033=\u8BBE\u5907\u9A8C\u8BC1\u7801\u9519\u8BEF +10034=\u53C2\u6570\u503C\u4E0D\u80FD\u4E3A\u7A7A +10035=\u53C2\u6570\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A +10036=\u4E0D\u652F\u6301\u7684\u53C2\u6570\u7C7B\u578B +10037=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684\u6570\u5B57 +10038=\u53C2\u6570\u503C\u5FC5\u987B\u662Ftrue\u6216false +10039=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u6570\u7EC4\u683C\u5F0F +10040=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u683C\u5F0F + +10041=\u8BBE\u5907\u672A\u627E\u5230 +10042={0} diff --git a/main/manager-api/src/main/resources/i18n/messages_en_US.properties b/main/manager-api/src/main/resources/i18n/messages_en_US.properties index 515de18a..43781857 100644 --- a/main/manager-api/src/main/resources/i18n/messages_en_US.properties +++ b/main/manager-api/src/main/resources/i18n/messages_en_US.properties @@ -32,4 +32,15 @@ 10030=The password is less than {0} digits. 10031=The password must consist of numbers, uppercase and lowercase letters, and special characters at the same time 10032=Exception in deleting this data -10033=Device verification code error \ No newline at end of file +10033=Device verification code error + +10034=Parameter value cannot be empty +10035=Parameter type cannot be empty +10036=Unsupported parameter type +10037=Parameter value must be a valid number +10038=Parameter value must be true or false +10039=Parameter value must be a valid JSON array format +10040=Parameter value must be a valid JSON format + +10041=Device not found +10042={0} \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties b/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties index 23b9abfa..713ab96a 100644 --- a/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties +++ b/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties @@ -32,4 +32,15 @@ 10030=\u5BC6\u7801\u957F\u5EA6\u4E0D\u8DB3{0}\u4F4D 10031=\u5BC6\u7801\u5FC5\u987B\u540C\u65F6\u5305\u542B\u6570\u5B57\u3001\u5927\u5C0F\u5B57\u6BCD\u548C\u7279\u6B8A\u5B57\u7B26\u7EC4\u6210 10032=\u5220\u9664\u672C\u6570\u636E\u5F02\u5E38 -10033=\u8BBE\u5907\u9A8C\u8BC1\u7801\u9519\u8BEF \ No newline at end of file +10033=\u8BBE\u5907\u9A8C\u8BC1\u7801\u9519\u8BEF + +10034=\u53C2\u6570\u503C\u4E0D\u80FD\u4E3A\u7A7A +10035=\u53C2\u6570\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A +10036=\u4E0D\u652F\u6301\u7684\u53C2\u6570\u7C7B\u578B +10037=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684\u6570\u5B57 +10038=\u53C2\u6570\u503C\u5FC5\u987B\u662Ftrue\u6216false +10039=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u6570\u7EC4\u683C\u5F0F +10040=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u683C\u5F0F + +10041=\u8BBE\u5907\u672A\u627E\u5230 +10042={0} \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties b/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties index 2a6bd8d0..b5847ec4 100644 --- a/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties +++ b/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties @@ -32,4 +32,15 @@ 10030=\u5BC6\u78BC\u9577\u5EA6\u4E0D\u8DB3{0}\u4F4D 10031=\u5BC6\u78BC\u5FC5\u9808\u540C\u6642\u5305\u542B\u6578\u5B57\u3001\u5927\u5C0F\u5B57\u6BCD\u548C\u7279\u6B8A\u5B57\u7B26\u7D44\u6210 10032=\u522A\u9664\u6B64\u6578\u64DA\u7570\u5E38 -10033=\u8A2D\u5099\u9A57\u8B49\u78BC\u932F\u8AA4 \ No newline at end of file +10033=\u8A2D\u5099\u9A57\u8B49\u78BC\u932F\u8AA4 + +10034=\u53C3\u6578\u503C\u4E0D\u80FD\u70BA\u7A7A +10035=\u53C3\u6578\u985E\u578B\u4E0D\u80FD\u70BA\u7A7A +10036=\u4E0D\u652F\u63F4\u7684\u53C3\u6578\u985E\u578B +10037=\u53C3\u6578\u503C\u5FC5\u9808\u662F\u6709\u6548\u7684\u6578\u5B57 +10038=\u53C3\u6578\u503C\u5FC5\u9808\u662Ftrue\u6216false +10039=\u53C3\u6578\u503C\u5FC5\u9808\u662F\u6709\u6548\u7684JSON\u6578\u7D44\u683C\u5F0F +10040=\u53C3\u6578\u503C\u5FC5\u9808\u662F\u6709\u6548\u7684JSON\u683C\u5F0F + +10041=\u8A2D\u5099\u672A\u627E\u5230 +10042={0} \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/validation.properties b/main/manager-api/src/main/resources/i18n/validation.properties index 008f2a03..3a10bf23 100644 --- a/main/manager-api/src/main/resources/i18n/validation.properties +++ b/main/manager-api/src/main/resources/i18n/validation.properties @@ -12,6 +12,8 @@ sysdict.label.require=\u5B57\u5178\u6807\u7B7E\u4E0D\u80FD\u4E3A\u7A7A sysparams.paramcode.require=\u53C2\u6570\u7F16\u7801\u4E0D\u80FD\u4E3A\u7A7A sysparams.paramvalue.require=\u53C2\u6570\u503C\u4E0D\u80FD\u4E3A\u7A7A +sysparams.valuetype.require=\u503C\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A +sysparams.valuetype.pattern=\u503C\u7C7B\u578B\u5FC5\u987B\u662F string\u3001number\u3001boolean \u6216 array sysuser.username.require=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A sysuser.password.require=\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A @@ -26,8 +28,8 @@ sysuser.uuid.require=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A timbre.languages.require=\u97F3\u8272\u7684\u8BED\u8A00\u4E0D\u53EF\u4EE5\u4E3A\u7A7A timbre.name.require=\u97F3\u8272\u7684\u540D\u79F0\u4E0D\u53EF\u4EE5\u4E3A\u7A7A -timbre.remark.require=\u97F3\u8272\u7684\u5907\u6CE8\u4E0D\u53EF\u4EE5\u4E3A\u7A7A timbre.ttsModelId.require=\u97F3\u8272\u7684tts\u4E3B\u952E\u4E0D\u53EF\u4EE5\u4E3A\u7A7A timbre.ttsVoice.require=\u97F3\u8272\u7684\u7F16\u7801\u4E0D\u53EF\u4EE5\u4E3A\u7A7A -timbre.voiceDemo.require=\u97F3\u8272\u7684\u64AD\u653EDemo\u4E0D\u53EF\u4EE5\u4E3A\u7A7A +ota.device.not.found=\u8BBE\u5907\u672A\u627E\u5230 +ota.device.need.bind={0} diff --git a/main/manager-api/src/main/resources/i18n/validation_en_US.properties b/main/manager-api/src/main/resources/i18n/validation_en_US.properties index 2840d605..342cada3 100644 --- a/main/manager-api/src/main/resources/i18n/validation_en_US.properties +++ b/main/manager-api/src/main/resources/i18n/validation_en_US.properties @@ -12,6 +12,8 @@ sysdict.label.require=Dictionary tag cannot be empty sysparams.paramcode.require=Parameter encoding cannot be empty sysparams.paramvalue.require=Parameter values cannot be empty +sysparams.valuetype.require=Value type cannot be empty +sysparams.valuetype.pattern=Value type must be string, number, boolean or array sysuser.username.require=The username cannot be empty sysuser.password.require=The password cannot be empty @@ -25,8 +27,8 @@ sysuser.uuid.require=The unique identifier cannot be empty timbre.languages.require=The language of the timbre cannot be empty timbre.name.require=The name of the timbre cannot be empty -timbre.remark.require=The remark of the timbre cannot be empty timbre.ttsModelId.require=The TTS model ID of the timbre cannot be empty timbre.ttsVoice.require=The TTS voice of the timbre cannot be empty -timbre.voiceDemo.require=The voice demo of the timbre cannot be empty +ota.device.not.found=Device not found +ota.device.need.bind={0} \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties b/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties index c86aded7..eaa50eb7 100644 --- a/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties +++ b/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties @@ -12,6 +12,8 @@ sysdict.label.require=\u5B57\u5178\u6807\u7B7E\u4E0D\u80FD\u4E3A\u7A7A sysparams.paramcode.require=\u53C2\u6570\u7F16\u7801\u4E0D\u80FD\u4E3A\u7A7A sysparams.paramvalue.require=\u53C2\u6570\u503C\u4E0D\u80FD\u4E3A\u7A7A +sysparams.valuetype.require=\u503C\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A +sysparams.valuetype.pattern=\u503C\u7C7B\u578B\u5FC5\u987B\u662F string\u3001number\u3001boolean \u6216 array sysuser.username.require=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A sysuser.password.require=\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A @@ -25,8 +27,8 @@ sysuser.uuid.require=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A timbre.languages.require=\u97F3\u8272\u7684\u8A9E\u8A00\u4E0D\u53EF\u4EE5\u70BA\u7A7A timbre.name.require=\u97F3\u8272\u7684\u540D\u7A31\u4E0D\u53EF\u4EE5\u70BA\u7A7A -timbre.remark.require=\u97F3\u8272\u7684\u5099\u6CE8\u4E0D\u53EF\u4EE5\u70BA\u7A7A timbre.ttsModelId.require=\u97F3\u8272\u7684tts\u4E3B\u9375\u4E0D\u53EF\u4EE5\u70BA\u7A7A timbre.ttsVoice.require=\u97F3\u8272\u7684\u7DE8\u78BC\u4E0D\u53EF\u4EE5\u70BA\u7A7A -timbre.voiceDemo.require=\u97F3\u8272\u7684\u64AD\u653E\u5730\u5740\u4E0D\u53EF\u4EE5\u70BA\u7A7A +ota.device.not.found=\u8A2D\u5099\u672A\u627E\u5230 +ota.device.need.bind={0} \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties b/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties index 23c59eef..4e3a271a 100644 --- a/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties +++ b/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties @@ -10,8 +10,10 @@ sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A sysdict.label.require=\u5B57\u5178\u6807\u7B7E\u4E0D\u80FD\u4E3A\u7A7A -sysparams.paramcode.require=\u53C2\u6570\u7F16\u7801\u4E0D\u80FD\u4E3A\u7A7A -sysparams.paramvalue.require=\u53C2\u6570\u503C\u4E0D\u80FD\u4E3A\u7A7A +sysparams.paramcode.require=\u53C3\u6578\u7DE8\u78BC\u4E0D\u80FD\u70BA\u7A7A +sysparams.paramvalue.require=\u53C3\u6578\u503C\u4E0D\u80FD\u70BA\u7A7A +sysparams.valuetype.require=\u503C\u985E\u578B\u4E0D\u80FD\u70BA\u7A7A +sysparams.valuetype.pattern=\u503C\u985E\u578B\u5FC5\u9808\u662F string\u3001number\u3001boolean \u6216 array sysuser.username.require=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A sysuser.password.require=\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A @@ -25,7 +27,8 @@ sysuser.uuid.require=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A timbre.languages.require=\u97F3\u8272\u7684\u8BED\u8A00\u4E0D\u53EF\u4EE5\u4E3A\u7A7A timbre.name.require=\u97F3\u8272\u7684\u540D\u79F0\u4E0D\u53EF\u4EE5\u4E3A\u7A7A -timbre.remark.require=\u97F3\u8272\u7684\u5907\u6CE8\u4E0D\u53EF\u4EE5\u4E3A\u7A7A timbre.ttsModelId.require=\u97F3\u8272\u7684tts\u4E3B\u952E\u4E0D\u53EF\u4EE5\u4E3A\u7A7A timbre.ttsVoice.require=\u97F3\u8272\u7684\u7F16\u7801\u4E0D\u53EF\u4EE5\u4E3A\u7A7A -timbre.voiceDemo.require=\u97F3\u8272\u7684\u64AD\u653EDemo\u4E0D\u53EF\u4EE5\u4E3A\u7A7A + +ota.device.not.found=\u8A2D\u5099\u672A\u627E\u5230 +ota.device.need.bind={0} \ No newline at end of file diff --git a/main/manager-api/src/main/resources/lua/emptyAll.lua b/main/manager-api/src/main/resources/lua/emptyAll.lua new file mode 100644 index 00000000..1d984cb7 --- /dev/null +++ b/main/manager-api/src/main/resources/lua/emptyAll.lua @@ -0,0 +1 @@ +redis.call('FLUSHALL') \ No newline at end of file diff --git a/main/manager-api/src/main/resources/lua/getKeyOrCreate.lua b/main/manager-api/src/main/resources/lua/getKeyOrCreate.lua new file mode 100644 index 00000000..bc375b12 --- /dev/null +++ b/main/manager-api/src/main/resources/lua/getKeyOrCreate.lua @@ -0,0 +1,11 @@ +local value = redis.call('GET', KEYS[1]) +-- value 如果为空着设置值 +if not value then + local result = redis.call('SET', KEYS[1], ARGV[1]) + -- 检查 ARGV[2] 是否存在且大于 0 + local expireTime = tonumber(ARGV[2]) + if expireTime and expireTime > 0 then + redis.call('EXPIRE', KEYS[1], expireTime) + end +end +return value \ No newline at end of file diff --git a/main/manager-api/src/main/resources/mapper/agent/AiAgentChatHistoryDao.xml b/main/manager-api/src/main/resources/mapper/agent/AiAgentChatHistoryDao.xml new file mode 100644 index 00000000..3e80f7fd --- /dev/null +++ b/main/manager-api/src/main/resources/mapper/agent/AiAgentChatHistoryDao.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + id, mac_address, agent_id, session_id, sort, chat_type, content, audio, audio_url, + created_at, updated_at + + + + DELETE FROM ai_agent_chat_audio + WHERE id IN ( + SELECT audio_id + FROM ai_agent_chat_history + WHERE agent_id = #{agentId} + ) + + + + UPDATE ai_agent_chat_history + SET audio_id = NULL + WHERE agent_id = #{agentId} + + + + DELETE FROM ai_agent_chat_history + WHERE agent_id = #{agentId} + + diff --git a/main/manager-api/src/main/resources/mapper/device/DeviceDao.xml b/main/manager-api/src/main/resources/mapper/device/DeviceDao.xml new file mode 100644 index 00000000..54de47c9 --- /dev/null +++ b/main/manager-api/src/main/resources/mapper/device/DeviceDao.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/main/manager-api/src/main/resources/mapper/sys/SysDictDataDao.xml b/main/manager-api/src/main/resources/mapper/sys/SysDictDataDao.xml index 7ea5e429..39848831 100644 --- a/main/manager-api/src/main/resources/mapper/sys/SysDictDataDao.xml +++ b/main/manager-api/src/main/resources/mapper/sys/SysDictDataDao.xml @@ -2,12 +2,17 @@ - - + SELECT d.dict_label AS `name`, d.dict_value AS `key` + FROM sys_dict_data d + LEFT JOIN sys_dict_type t ON d.dict_type_id = t.id + WHERE t.dict_type = #{dictType} + ORDER BY d.sort ASC - + SELECT dict_type + FROM sys_dict_type + WHERE id = #{dictTypeId} \ No newline at end of file diff --git a/main/manager-api/src/main/resources/mapper/sys/SysDictTypeDao.xml b/main/manager-api/src/main/resources/mapper/sys/SysDictTypeDao.xml index 11887552..825db02b 100644 --- a/main/manager-api/src/main/resources/mapper/sys/SysDictTypeDao.xml +++ b/main/manager-api/src/main/resources/mapper/sys/SysDictTypeDao.xml @@ -3,8 +3,4 @@ - - \ No newline at end of file diff --git a/main/manager-api/src/test/java/xiaozhi/modules/device/DeviceTest.java b/main/manager-api/src/test/java/xiaozhi/modules/device/DeviceTest.java index 8647692c..37f67cab 100644 --- a/main/manager-api/src/test/java/xiaozhi/modules/device/DeviceTest.java +++ b/main/manager-api/src/test/java/xiaozhi/modules/device/DeviceTest.java @@ -1,5 +1,8 @@ package xiaozhi.modules.device; +import java.util.HashMap; +import java.util.UUID; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -8,8 +11,9 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import lombok.extern.slf4j.Slf4j; -import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; +import xiaozhi.modules.sys.dto.SysUserDTO; +import xiaozhi.modules.sys.service.SysUserService; @Slf4j @SpringBootTest @@ -19,18 +23,37 @@ public class DeviceTest { @Autowired private RedisUtils redisUtils; + @Autowired + private SysUserService sysUserService; + + @Test + public void testSaveUser() { + SysUserDTO userDTO = new SysUserDTO(); + userDTO.setUsername("test"); + userDTO.setPassword(UUID.randomUUID().toString()); + sysUserService.save(userDTO); + } @Test @DisplayName("测试写入设备信息") public void testWriteDeviceInfo() { log.info("开始测试写入设备信息..."); - // 模拟设备MAC地址 - String macAddress = "00:11:22:33:44:55"; + String macAddress = "00:11:22:33:44:66"; // 模拟设备验证码 String deviceCode = "123456"; - String redisKey = RedisKeys.getDeviceCaptchaKey(deviceCode); + HashMap map = new HashMap<>(); + map.put("mac_address", macAddress); + map.put("activation_code", deviceCode); + map.put("board", "硬件型号"); + map.put("app_version", "0.3.13"); + + String safeDeviceId = macAddress.replace(":", "_").toLowerCase(); + String cacheDeviceKey = String.format("ota:activation:data:%s", safeDeviceId); + redisUtils.set(cacheDeviceKey, map, 300); + + String redisKey = "ota:activation:code:" + deviceCode; log.info("Redis Key: {}", redisKey); // 将设备信息写入Redis diff --git a/main/manager-api/src/test/java/xiaozhi/modules/sys/loginControllerTest.java b/main/manager-api/src/test/java/xiaozhi/modules/sys/loginControllerTest.java new file mode 100644 index 00000000..5cc39967 --- /dev/null +++ b/main/manager-api/src/test/java/xiaozhi/modules/sys/loginControllerTest.java @@ -0,0 +1,58 @@ +package xiaozhi.modules.sys; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import lombok.extern.slf4j.Slf4j; +import xiaozhi.modules.security.controller.LoginController; +import xiaozhi.modules.security.dto.LoginDTO; +import xiaozhi.modules.security.dto.SmsVerificationDTO; +import xiaozhi.modules.sys.dto.RetrievePasswordDTO; + +@Slf4j +@SpringBootTest +@ActiveProfiles("dev") +class loginControllerTest { + + @Autowired + LoginController loginController; + + @Test + public void testRegister() { + LoginDTO loginDTO = new LoginDTO(); + loginDTO.setUsername("手机号码"); + loginDTO.setPassword("密码"); + loginDTO.setCaptcha("123456"); + loginController.register(loginDTO); + } + + @Test + public void testSmsVerification() { + try { + SmsVerificationDTO smsVerificationDTO = new SmsVerificationDTO(); + smsVerificationDTO.setPhone("手机号码"); + smsVerificationDTO.setCaptchaId("123456"); + smsVerificationDTO.setCaptcha("123456"); + loginController.smsVerification(smsVerificationDTO); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } + + @Test + public void testRetrievePassword() { + try { + RetrievePasswordDTO retrievePasswordDTO = new RetrievePasswordDTO(); + retrievePasswordDTO.setCode("123456"); + retrievePasswordDTO.setPhone("手机号码"); + retrievePasswordDTO.setPassword("密码"); + loginController.retrievePassword(retrievePasswordDTO); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + + } + +} \ No newline at end of file diff --git a/main/manager-web/.env.development b/main/manager-web/.env.development index ced1a9e7..0a1de306 100644 --- a/main/manager-web/.env.development +++ b/main/manager-web/.env.development @@ -1,4 +1 @@ -# 暂时使用群主的接口 -# VUE_APP_API_BASE_URL=https://2662r3426b.vicp.fun/xiaozhi -# 如果本地开发,请使用以下接口 -VUE_APP_API_BASE_URL=http://localhost:8002/xiaozhi \ No newline at end of file +VUE_APP_API_BASE_URL=/xiaozhi \ No newline at end of file diff --git a/main/manager-web/.env.production b/main/manager-web/.env.production index 0a1de306..351d791a 100644 --- a/main/manager-web/.env.production +++ b/main/manager-web/.env.production @@ -1 +1,4 @@ -VUE_APP_API_BASE_URL=/xiaozhi \ No newline at end of file +VUE_APP_API_BASE_URL=/xiaozhi +VUE_APP_PUBLIC_PATH=/ +# 是否开启CDN +VUE_APP_USE_CDN=false \ No newline at end of file diff --git a/main/manager-web/README.md b/main/manager-web/README.md index 0f793bb0..d0a34ad1 100644 --- a/main/manager-web/README.md +++ b/main/manager-web/README.md @@ -1,4 +1,4 @@ -本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../../../docs/Deployment.md) +本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../../README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3) # xiaozhi diff --git a/main/manager-web/package-lock.json b/main/manager-web/package-lock.json index f84c4950..c104b60c 100644 --- a/main/manager-web/package-lock.json +++ b/main/manager-web/package-lock.json @@ -31,7 +31,8 @@ "sass": "^1.32.7", "sass-loader": "^12.0.0", "vue-template-compiler": "^2.6.14", - "webpack-bundle-analyzer": "^4.10.2" + "webpack-bundle-analyzer": "^4.10.2", + "workbox-webpack-plugin": "^7.3.0" } }, "node_modules/@achrinza/node-ipc": { @@ -53,7 +54,6 @@ "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -90,7 +90,6 @@ "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.26.10.tgz", "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -132,6 +131,18 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-compilation-targets": { "version": "7.27.0", "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", @@ -148,6 +159,44 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", + "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.27.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.0.tgz", + "integrity": "sha512-fO8l08T76v48BhpNRW/nQ0MxfnSdoSKUJBMjubOAYffsVuGG5qOfMq7N6Es7UJvi7Y8goXXo07EfcHZXDPuELQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/helper-define-polyfill-provider": { "version": "0.6.4", "resolved": "https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", @@ -164,6 +213,19 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-module-imports": { "version": "7.25.9", "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", @@ -182,7 +244,6 @@ "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9", @@ -195,6 +256,18 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-plugin-utils": { "version": "7.26.5", "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", @@ -204,6 +277,53 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.26.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", + "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.25.9", "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", @@ -229,12 +349,25 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helpers": { "version": "7.27.0", "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.27.0.tgz", "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", "dev": true, - "peer": true, "dependencies": { "@babel/template": "^7.27.0", "@babel/types": "^7.27.0" @@ -257,6 +390,97 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-dynamic-import": { "version": "7.8.3", "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", @@ -269,6 +493,700 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.26.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.26.8", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", + "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.26.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.26.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", + "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.0.tgz", + "integrity": "sha512-u1jGphZ8uDI2Pj/HJj6YQ6XQLZCNjOlprjxB5SVz6rq2T6SwAR+CdrWK0CP7F+9rDVMXdB0+r6Am5G5aobOjAQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.26.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.26.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.26.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz", + "integrity": "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.26.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.26.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", + "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.0.tgz", + "integrity": "sha512-LX/vCajUJQDqE7Aum/ELUMZAY19+cDpghxrnyt5I1tV6X5PyC86AOoWXWFYFeIvauyeSA6/ktn4tQVn/3ZifsA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.26.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", + "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-runtime": { "version": "7.26.10", "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz", @@ -289,6 +1207,260 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.26.8", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz", + "integrity": "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.0.tgz", + "integrity": "sha512-+LLkxA9rKJpNoGsbLnAgOCdESl73vwYn+V6b+5wHbrE7OGKVDPHIQvbFSzqE6rwqaCw2RE+zdJrlLkcf8YOA0w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.26.9", + "resolved": "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.26.9.tgz", + "integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.26.8", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.26.8", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.26.5", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.26.3", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.26.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.26.3", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.26.8", + "@babel/plugin-transform-typeof-symbol": "^7.26.7", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.40.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmmirror.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, "node_modules/@babel/template": { "version": "7.27.0", "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.27.0.tgz", @@ -775,6 +1947,160 @@ "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", "dev": true }, + "node_modules/@rollup/plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", + "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.1", + "resolved": "https://registry.npmmirror.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", + "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve/node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@rollup/plugin-node-resolve/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@rollup/plugin-node-resolve/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmmirror.com/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/pluginutils/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmmirror.com/@sideway/address/-/address-4.1.5.tgz", @@ -820,6 +2146,18 @@ "integrity": "sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w==", "dev": true }, + "node_modules/@surma/rollup-plugin-off-main-thread": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", + "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", + "dev": true, + "dependencies": { + "ejs": "^3.1.6", + "json5": "^2.2.0", + "magic-string": "^0.25.0", + "string.prototype.matchall": "^4.0.6" + } + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmmirror.com/@trysound/sax/-/sax-0.2.0.tgz", @@ -1010,6 +2348,12 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmmirror.com/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmmirror.com/@types/retry/-/retry-0.12.0.tgz", @@ -1055,6 +2399,12 @@ "@types/node": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true + }, "node_modules/@types/ws": { "version": "8.18.0", "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.18.0.tgz", @@ -1743,6 +3093,22 @@ } ] }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", @@ -1758,6 +3124,27 @@ "node": ">=8" } }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmmirror.com/asn1/-/asn1-0.2.6.tgz", @@ -1780,6 +3167,15 @@ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/async-validator": { "version": "1.8.5", "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-1.8.5.tgz", @@ -1839,6 +3235,21 @@ "postcss": "^8.1.0" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmmirror.com/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -2164,6 +3575,24 @@ "node": ">= 0.8" } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2465,6 +3894,15 @@ "node": ">= 12" } }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmmirror.com/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz", @@ -2583,8 +4021,7 @@ "version": "2.0.0", "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/cookie": { "version": "0.7.1", @@ -2717,6 +4154,15 @@ "node": ">= 8" } }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmmirror.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -2971,6 +4417,57 @@ "node": ">=0.10" } }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz", @@ -3091,6 +4588,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -3100,6 +4614,23 @@ "node": ">=8" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3302,6 +4833,21 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.128", "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.128.tgz", @@ -3397,6 +4943,71 @@ "stackframe": "^1.3.4" } }, + "node_modules/es-abstract": { + "version": "1.23.9", + "resolved": "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", @@ -3434,7 +5045,6 @@ "version": "2.1.0", "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "peer": true, "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -3445,6 +5055,23 @@ "node": ">= 0.4" } }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", @@ -3512,6 +5139,21 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", @@ -3806,6 +5448,36 @@ "node": ">=4" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", @@ -3900,6 +5572,21 @@ } } }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmmirror.com/forever-agent/-/forever-agent-0.6.1.tgz", @@ -4003,12 +5690,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, - "peer": true, "engines": { "node": ">=6.9.0" } @@ -4045,6 +5760,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", @@ -4069,6 +5790,23 @@ "node": ">=6" } }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmmirror.com/getpass/-/getpass-0.1.7.tgz", @@ -4125,6 +5863,22 @@ "node": ">=4" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz", @@ -4204,6 +5958,18 @@ "node": ">=6" } }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", @@ -4213,6 +5979,33 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", @@ -4228,7 +6021,6 @@ "version": "1.0.2", "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "peer": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -4529,6 +6321,12 @@ "postcss": "^8.1.0" } }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "dev": true + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", @@ -4597,6 +6395,20 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -4606,12 +6418,63 @@ "node": ">= 10" } }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -4624,6 +6487,34 @@ "node": ">=8" } }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", @@ -4639,6 +6530,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-2.2.1.tgz", @@ -4672,6 +6596,21 @@ "read-pkg-up": "^7.0.1" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -4681,6 +6620,24 @@ "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", @@ -4702,6 +6659,24 @@ "node": ">=8" } }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", @@ -4711,6 +6686,31 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -4735,6 +6735,60 @@ "node": ">=0.10.0" } }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-1.1.0.tgz", @@ -4744,6 +6798,54 @@ "node": ">=0.10.0" } }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -4761,6 +6863,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz", @@ -4798,6 +6943,40 @@ "resolved": "https://registry.npmmirror.com/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmmirror.com/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/javascript-stringify": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/javascript-stringify/-/javascript-stringify-2.1.0.tgz", @@ -4929,6 +7108,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jsprim": { "version": "1.4.2", "resolved": "https://registry.npmmirror.com/jsprim/-/jsprim-1.4.2.tgz", @@ -4980,6 +7168,15 @@ "launch-editor": "^2.10.0" } }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.1.0.tgz", @@ -5072,6 +7269,12 @@ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmmirror.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true + }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -5244,6 +7447,15 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5697,6 +7909,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmmirror.com/obuf/-/obuf-1.1.2.tgz", @@ -5830,6 +8071,23 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/p-finally/-/p-finally-1.0.0.tgz", @@ -6050,6 +8308,15 @@ "node": ">= 10.12" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.5.3", "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz", @@ -6635,6 +8902,18 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmmirror.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/pretty-error/-/pretty-error-4.0.0.tgz", @@ -6922,11 +9201,127 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmmirror.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "dev": true, + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/relateurl": { "version": "0.2.7", "resolved": "https://registry.npmmirror.com/relateurl/-/relateurl-0.2.7.tgz", @@ -7099,6 +9494,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "2.79.2", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", @@ -7122,6 +9532,31 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7141,6 +9576,45 @@ } ] }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -7435,6 +9909,52 @@ "node": ">= 0.8.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -7594,6 +10114,12 @@ "node": ">=8" } }, + "node_modules/smob": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", + "dev": true + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmmirror.com/sockjs/-/sockjs-0.3.24.tgz", @@ -7614,6 +10140,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", @@ -7640,6 +10172,13 @@ "source-map": "^0.6.0" } }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmmirror.com/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -7783,6 +10322,103 @@ "node": ">=8" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmmirror.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -7795,6 +10431,15 @@ "node": ">=8" } }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/strip-eof/-/strip-eof-1.0.0.tgz", @@ -7892,6 +10537,57 @@ "node": ">=6" } }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/terser": { "version": "5.39.0", "resolved": "https://registry.npmmirror.com/terser/-/terser-5.39.0.tgz", @@ -8132,12 +10828,156 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.20.0.tgz", "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", @@ -8156,6 +10996,16 @@ "node": ">= 0.8" } }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -8741,12 +11591,400 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-2.0.1.tgz", "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, + "node_modules/workbox-background-sync": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-background-sync/-/workbox-background-sync-7.3.0.tgz", + "integrity": "sha512-PCSk3eK7Mxeuyatb22pcSx9dlgWNv3+M8PqPaYDokks8Y5/FX4soaOqj3yhAZr5k6Q5JWTOMYgaJBpbw11G9Eg==", + "dev": true, + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.3.0" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-broadcast-update/-/workbox-broadcast-update-7.3.0.tgz", + "integrity": "sha512-T9/F5VEdJVhwmrIAE+E/kq5at2OY6+OXXgOWQevnubal6sO92Gjo24v6dCVwQiclAF5NS3hlmsifRrpQzZCdUA==", + "dev": true, + "dependencies": { + "workbox-core": "7.3.0" + } + }, + "node_modules/workbox-build": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-build/-/workbox-build-7.3.0.tgz", + "integrity": "sha512-JGL6vZTPlxnlqZRhR/K/msqg3wKP+m0wfEUVosK7gsYzSgeIxvZLi1ViJJzVL7CEeI8r7rGFV973RiEqkP3lWQ==", + "dev": true, + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.24.4", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^5.2.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^2.4.1", + "@rollup/plugin-terser": "^0.4.3", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^7.1.6", + "lodash": "^4.17.20", + "pretty-bytes": "^5.3.0", + "rollup": "^2.43.1", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "7.3.0", + "workbox-broadcast-update": "7.3.0", + "workbox-cacheable-response": "7.3.0", + "workbox-core": "7.3.0", + "workbox-expiration": "7.3.0", + "workbox-google-analytics": "7.3.0", + "workbox-navigation-preload": "7.3.0", + "workbox-precaching": "7.3.0", + "workbox-range-requests": "7.3.0", + "workbox-recipes": "7.3.0", + "workbox-routing": "7.3.0", + "workbox-strategies": "7.3.0", + "workbox-streams": "7.3.0", + "workbox-sw": "7.3.0", + "workbox-window": "7.3.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/workbox-build/node_modules/@apideck/better-ajv-errors": { + "version": "0.3.6", + "resolved": "https://registry.npmmirror.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", + "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "dev": true, + "dependencies": { + "json-schema": "^0.4.0", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/workbox-build/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/workbox-build/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/workbox-build/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workbox-build/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/workbox-build/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "node_modules/workbox-build/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-cacheable-response/-/workbox-cacheable-response-7.3.0.tgz", + "integrity": "sha512-eAFERIg6J2LuyELhLlmeRcJFa5e16Mj8kL2yCDbhWE+HUun9skRQrGIFVUagqWj4DMaaPSMWfAolM7XZZxNmxA==", + "dev": true, + "dependencies": { + "workbox-core": "7.3.0" + } + }, + "node_modules/workbox-core": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-core/-/workbox-core-7.3.0.tgz", + "integrity": "sha512-Z+mYrErfh4t3zi7NVTvOuACB0A/jA3bgxUN3PwtAVHvfEsZxV9Iju580VEETug3zYJRc0Dmii/aixI/Uxj8fmw==", + "dev": true + }, + "node_modules/workbox-expiration": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-expiration/-/workbox-expiration-7.3.0.tgz", + "integrity": "sha512-lpnSSLp2BM+K6bgFCWc5bS1LR5pAwDWbcKt1iL87/eTSJRdLdAwGQznZE+1czLgn/X05YChsrEegTNxjM067vQ==", + "dev": true, + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.3.0" + } + }, + "node_modules/workbox-google-analytics": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-google-analytics/-/workbox-google-analytics-7.3.0.tgz", + "integrity": "sha512-ii/tSfFdhjLHZ2BrYgFNTrb/yk04pw2hasgbM70jpZfLk0vdJAXgaiMAWsoE+wfJDNWoZmBYY0hMVI0v5wWDbg==", + "dev": true, + "dependencies": { + "workbox-background-sync": "7.3.0", + "workbox-core": "7.3.0", + "workbox-routing": "7.3.0", + "workbox-strategies": "7.3.0" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-navigation-preload/-/workbox-navigation-preload-7.3.0.tgz", + "integrity": "sha512-fTJzogmFaTv4bShZ6aA7Bfj4Cewaq5rp30qcxl2iYM45YD79rKIhvzNHiFj1P+u5ZZldroqhASXwwoyusnr2cg==", + "dev": true, + "dependencies": { + "workbox-core": "7.3.0" + } + }, + "node_modules/workbox-precaching": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-precaching/-/workbox-precaching-7.3.0.tgz", + "integrity": "sha512-ckp/3t0msgXclVAYaNndAGeAoWQUv7Rwc4fdhWL69CCAb2UHo3Cef0KIUctqfQj1p8h6aGyz3w8Cy3Ihq9OmIw==", + "dev": true, + "dependencies": { + "workbox-core": "7.3.0", + "workbox-routing": "7.3.0", + "workbox-strategies": "7.3.0" + } + }, + "node_modules/workbox-range-requests": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-range-requests/-/workbox-range-requests-7.3.0.tgz", + "integrity": "sha512-EyFmM1KpDzzAouNF3+EWa15yDEenwxoeXu9bgxOEYnFfCxns7eAxA9WSSaVd8kujFFt3eIbShNqa4hLQNFvmVQ==", + "dev": true, + "dependencies": { + "workbox-core": "7.3.0" + } + }, + "node_modules/workbox-recipes": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-recipes/-/workbox-recipes-7.3.0.tgz", + "integrity": "sha512-BJro/MpuW35I/zjZQBcoxsctgeB+kyb2JAP5EB3EYzePg8wDGoQuUdyYQS+CheTb+GhqJeWmVs3QxLI8EBP1sg==", + "dev": true, + "dependencies": { + "workbox-cacheable-response": "7.3.0", + "workbox-core": "7.3.0", + "workbox-expiration": "7.3.0", + "workbox-precaching": "7.3.0", + "workbox-routing": "7.3.0", + "workbox-strategies": "7.3.0" + } + }, + "node_modules/workbox-routing": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-routing/-/workbox-routing-7.3.0.tgz", + "integrity": "sha512-ZUlysUVn5ZUzMOmQN3bqu+gK98vNfgX/gSTZ127izJg/pMMy4LryAthnYtjuqcjkN4HEAx1mdgxNiKJMZQM76A==", + "dev": true, + "dependencies": { + "workbox-core": "7.3.0" + } + }, + "node_modules/workbox-strategies": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-strategies/-/workbox-strategies-7.3.0.tgz", + "integrity": "sha512-tmZydug+qzDFATwX7QiEL5Hdf7FrkhjaF9db1CbB39sDmEZJg3l9ayDvPxy8Y18C3Y66Nrr9kkN1f/RlkDgllg==", + "dev": true, + "dependencies": { + "workbox-core": "7.3.0" + } + }, + "node_modules/workbox-streams": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-streams/-/workbox-streams-7.3.0.tgz", + "integrity": "sha512-SZnXucyg8x2Y61VGtDjKPO5EgPUG5NDn/v86WYHX+9ZqvAsGOytP0Jxp1bl663YUuMoXSAtsGLL+byHzEuMRpw==", + "dev": true, + "dependencies": { + "workbox-core": "7.3.0", + "workbox-routing": "7.3.0" + } + }, + "node_modules/workbox-sw": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-sw/-/workbox-sw-7.3.0.tgz", + "integrity": "sha512-aCUyoAZU9IZtH05mn0ACUpyHzPs0lMeJimAYkQkBsOWiqaJLgusfDCR+yllkPkFRxWpZKF8vSvgHYeG7LwhlmA==", + "dev": true + }, + "node_modules/workbox-webpack-plugin": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-webpack-plugin/-/workbox-webpack-plugin-7.3.0.tgz", + "integrity": "sha512-EC8lmSAuNmPli04+a5r5lTgv8ab+f5l+XjdYuYpbGnxDT15kH6DBeBazVslpffqTDHt+wkdBMnBCu8GdkKrTSA==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "^2.1.0", + "pretty-bytes": "^5.4.1", + "upath": "^1.2.0", + "webpack-sources": "^1.4.3", + "workbox-build": "7.3.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "webpack": "^4.4.0 || ^5.91.0" + } + }, + "node_modules/workbox-webpack-plugin/node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/workbox-window": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/workbox-window/-/workbox-window-7.3.0.tgz", + "integrity": "sha512-qW8PDy16OV1UBaUNGlTVcepzrlzyzNW/ZJvFQQs2j2TzGsg6IKjcpZC1RSquqQnTOafl5pCj5bGfAHlCjOOjdA==", + "dev": true, + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "7.3.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -8847,4 +12085,4 @@ } } } -} \ No newline at end of file +} diff --git a/main/manager-web/package.json b/main/manager-web/package.json index e49c09a8..55e275d9 100644 --- a/main/manager-web/package.json +++ b/main/manager-web/package.json @@ -10,6 +10,7 @@ "dependencies": { "core-js": "^3.41.0", "cross-env": "^7.0.3", + "dotenv": "^16.5.0", "element-ui": "^2.15.14", "flyio": "^0.6.14", "normalize.css": "^8.0.1", @@ -31,7 +32,8 @@ "sass": "^1.32.7", "sass-loader": "^12.0.0", "vue-template-compiler": "^2.6.14", - "webpack-bundle-analyzer": "^4.10.2" + "webpack-bundle-analyzer": "^4.10.2", + "workbox-webpack-plugin": "^7.3.0" }, "browserslist": [ "> 1%", @@ -43,4 +45,4 @@ "*.scss", "*.vue" ] -} \ No newline at end of file +} diff --git a/main/manager-web/public/encoderWorker.min.js b/main/manager-web/public/encoderWorker.min.js deleted file mode 100644 index e53ae167..00000000 --- a/main/manager-web/public/encoderWorker.min.js +++ /dev/null @@ -1 +0,0 @@ -var Module=typeof Module!=="undefined"?Module:{};var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=typeof window==="object";var ENVIRONMENT_IS_WORKER=typeof importScripts==="function";var ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;function logExceptionOnExit(e){if(e instanceof ExitStatus)return;var toLog=e;err("exiting due to exception: "+toLog)}var nodeFS;var nodePath;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=function shell_read(filename,binary){var ret=tryParseAsDataURI(filename);if(ret){return binary?ret:ret.toString()}if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);return nodeFS["readFileSync"](filename,binary?null:"utf8")};readBinary=function readBinary(filename){var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};readAsync=function readAsync(filename,onload,onerror){var ret=tryParseAsDataURI(filename);if(ret){onload(ret)}if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);nodeFS["readFile"](filename,function(err,data){if(err)onerror(err);else onload(data.buffer)})};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);if(typeof module!=="undefined"){module["exports"]=Module}process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",function(reason){throw reason});quit_=function(status,toThrow){if(keepRuntimeAlive()){process["exitCode"]=status;throw toThrow}logExceptionOnExit(toThrow);process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!=="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=function(url){try{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText}catch(err){var data=tryParseAsDataURI(url);if(data){return intArrayToString(data)}throw err}};if(ENVIRONMENT_IS_WORKER){readBinary=function(url){try{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}catch(err){var data=tryParseAsDataURI(url);if(data){return data}throw err}}}readAsync=function(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}var data=tryParseAsDataURI(url);if(data){onload(data.buffer);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!=="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort("Assertion failed: "+text)}}var UTF8Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heap,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heap[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heap.subarray&&UTF8Decoder){return UTF8Decoder.decode(heap.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeKeepaliveCounter=0;function keepRuntimeAlive(){return noExitRuntime||runtimeKeepaliveCounter>0}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){{if(Module["onAbort"]){Module["onAbort"](what)}}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -s ASSERTIONS=1 for more info.";var e=new WebAssembly.RuntimeError(what);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}var wasmBinaryFile;wasmBinaryFile="data:application/octet-stream;base64,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DWogDGpBAXRqLgEAQQN1IgYgBmxqIAggAkEBciANaiAMakEBdGouAQBBA3UiACAAbGohACACQQJqIQIgC0ECayILDQALCyAPBH8gACAIIAIgDWogDGpBAXRqLgEAQQN1IgAgAGxqBSAACyAFaiIAQf////8HIABBf0obIQVBACECIAQNACATQQNsIQwgEigCACEEAkAgDkUEQEEAIQAMAQsgFEH+////B3EhC0EAIQADQCACIAggACAMaiAEakEBdGouAQBBA3UiBiAGbGogCCAAQQFyIAxqIARqQQF0ai4BAEEDdSICIAJsaiECIABBAmohACALQQJrIgsNAAsLIA9FDQAgAiAIIAAgDGogBGpBAXRqLgEAQQN1IgAgAGxqIQILIAcgBSACQQF2aiIAQf////8HIABBf0obNgIAIAkgAjYCPCARQQFqIhFBBEcNAAsMAgtBsjBByzxB6AAQBgALQbLWAEHLPEHqABAGAAtBACECIAMoApABIgBB5wdMBEAgAyAAQQFqNgKQAUH//wEgAEEEdUEBam0hAgtBgAEhBUH/////ByAKKAIgIgYgAygCgAFqIgBB/////wcgAEF/ShsiAG4hByADAn9BgAEgACADKAJgIgRBA3RKDQAaQYAIIAAgBEgNABogBEEQdEEQdSIAIAdBEHZsIAcgBEEPdUEBakEBdWxqIAdB//8DcSAAbEEQdWoiAEEFdUGAcHEgAEEFdkH/D3FyCyIAIAIgACACShtBEHRBEHUiCSAHIAMoAnAiBGsiAEEQdWwgBGogCSAAQf//A3FsQRB1aiIANgJwIANB/////wcgAG0iAEH///8HIABB////B0gbIhE2AmBB/////wcgCigCJCINIAMoAoQBaiIAQf////8HIABBf0obIgBuIQcCQCAAIAMoAmQiBEEDdEoNAEGACCEFIAAgBEgNACAEQRB0QRB1IgAgB0EQdmwgByAEQQ91QQFqQQF1bGogB0H//wNxIABsQRB1aiIAQQV1QYBwcSAAQQV2Qf8PcXIhBQsgAyAFIAIgAiAFSBtBEHRBEHUiCSAHIAMoAnQiBGsiAEEQdWwgBGogCSAAQf//A3FsQRB1aiIANgJ0IANB/////wcgAG0iAEH///8HIABB////B0gbNgJkQf////8HIAooAigiDCADKAKIAWoiAEH/////ByAAQX9K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AgABAgsgAEHAPjYCFEEAIQIMJgsgAEHg3QA2AhRBACECDCULIABBgP0ANgIUQQAhAgwkCyADIAMoAswBIgFBBGo2AswBIAEoAgAiAUUEQEF/IQIMJAsgASAAKAKEATYCAEEAIQIMIwsgAyADKALMASIBQQRqNgLMASABKAIAIgFBzQhrIgRBBU8EQEF/IQIgAUGYeEcNIwsgACABNgKAAQJAAkACQCAEDgIAAQILIABBwD42AhRBACECDCQLIABB4N0ANgIUQQAhAgwjCyAAQYD9ADYCFEEAIQIMIgsgAyADKALMASIBQQRqNgLMASABKAIAIgFFBEBBfyECDCILIAEgACgCoG82AgBBACECDCELIAMgAygCzAEiAUEEajYCzAFBfyECIAEoAgAiAUEBSw0gIAAgATYCuAFBACECDCALIAMgAygCzAEiAUEEajYCzAEgASgCACIBRQRAQX8hAgwgCyABIAAoArgBNgIAQQAhAgwfCyADIAMoAswBIgFBBGo2AswBQX8hAiABKAIAIgFBCksNHiAAIAE2AiwgAyABNgIAIARBqh8gAxAKGkEAIQIMHgsgAyADKALMASIBQQRqNgLMASABKAIAIgFFBEBBfyECDB4LIAEgACgCLDYCAEEAIQIMHQsgAyADKALMASIBQQRqNgLMAUF/IQIgASgCACIBQQFLDRwgACABNgIwQQAhAgwcCyADIAMoAswBIgFBBGo2AswBIAEoAgAiAUUEQEF/IQIMHAsgASAAKAIwNgIAQQAhAgwbCyADIAMoAswBIgFBBGo2AswBQX8hAiABKAIAIgFB5ABLDRogACABNgIoIAMgATYCECAEQa4fIANBEGoQChpBACECDBoLIAMgAygCzAEiAUEEajYCzAEgASgCACIBRQRAQX8hAgwaCyABIAAoAig2AgBBACECDBkLIAMgAygCzAEiAUEEajYCzAFBfyECIAEoAgAiAUEBSw0YIAAgATYClAEgAEEBIAFrNgI8QQAhAgwYCyADIAMoAswBIgFBBGo2AswBIAEoAgAiAUUEQEF/IQIMGAsgASAAKAKUATYCAEEAIQIMFwsgAyADKALMASIBQQRqNgLMAUF/IQIgASgCACIBQQFqQeUASw0WIAAgATYCjAFBACECDBYLIAMgAygCzAEiAUEEajYCzAEgASgCACIBRQRAQX8hAgwWCyABIAAoAowBNgIAQQAhAgwVCyADIAMoAswBIgFBBGo2AswBQX8hAiABKAIAIgFBAUsNFCAAIAE2ApgBQQAhAgwUCyADIAMoAswBIgFBBGo2AswBIAEoAgAiAUUEQEF/IQIMFAsgASAAKAKYATYCAEEAIQIMEwsgAyADKALMASIBQQRqNgLMASABKAIAIgFBuRdrQQJPBEBBfyECIAFBmHhHDRMLIAAgATYCfEEAIQIMEgsgAyADKALMASIBQQRqNgLMASABKAIAIgFFBEBBfyECDBILIAEgACgCfDYCAEEAIQIMEQsgAyADKALMASIBQQRqNgLMASABKAIAIgFFBEBBfyECDBELIAEgACgCkAFBkANtIgQ2AgBBACECIAAoAmxBgxBGDRAgASAAKAJ0IARqNgIADBALIAMgAygCzAEiAUEEajYCzAEgASgCACIBRQRAQX8hAgwQCyABIAAoApABNgIAQQAhAgwPCyADIAMoAswBIgFBBGo2AswBIAEoAgAiAUUEQEF/IQIMDwsgASAAKALYjQE2AgBBACECDA4LIAMgAygCzAEiAUEEajYCzAFBfyECIAEoAgAiAUEIa0EQSw0NIAAgATYCqAFBACECDA0LIAMgAygCzAEiAUEEajYCzAEgASgCACIBRQRAQX8hAgwNCyABIAAoAqgBNgIAQQAhAgwMCyADIAMoAswBIgFBBGo2AswBQX8hAiABKAIAIgFBiCdrQQlLDQsgACABNgKcAUEAIQIMCwsgAyADKALMASIBQQRqNgLMASABKAIAIgFFBEBBfyECDAsLIAEgACgCnAE2AgBBACECDAoLIAMgAygCzAEiAUEEajYCzAFBfyECIAEoAgAiAUEBSw0JIAAgATYCTEEAIQIMCQsgAyADKALMASIBQQRqNgLMASABKAIAIgFFBEBBfyECDAkLIAEgACgCTDYCAEEAIQIMCAsgAyADKALMASIAQQRqNgLMAUF/IQIgACgCACIAQQFLDQcgAyAANgIgIARBzh8gA0EgahAKGkEAIQIMBwsgAyADKALMASIAQQRqNgLMASAAKAIAIgBFBEBBfyECDAcLIAMgADYCMCAEQc8fIANBMGoQChpBACECDAYLIAAoAgQhASAAQbwBahBwQQAhAiAAQfDuAGpBAEHsHhAJGiAEQbwfQQAQChogACABaiAAKAK0ASADQegAahBBGiAAQQE2AqxvIABBgICA/AM2AvxuIABBgIABOwH0biAAQdEINgKgbyAAQekHNgKQbyAAIAAoAnA2AvBuIABBPBALQQh0NgL4bgwFCyADIAMoAswBIgFBBGo2AswBIAEoAgAiAUHoB2tBA08EQEF/IQIgAUGYeEcNBQsgACABNgKIAUEAIQIMBAsgAyADKALMASIBQQRqNgLMASAAIAEoAgAiADYCsAEgAyAANgJAIARBqM4AIANBQGsQCiECDAMLIAMgAygCzAEiAUEEajYCzAEgACABKAIAIgA2ArBvIAMgADYCUCAEQarOACADQdAAahAKIQIMAgsgAyADKALMASIBQQRqNgLMASABKAIAIgVFBEBBfyECDAILAkAgACgCOEUNACAAKAKUb0F+cUHoB0cNACAAKAIEIQFBASEEIAVBATYCACAAKAIMQQFIBEBBACECDAMLIAAgAWohBwNAQQAhAkEAIQEgBARAIAcgBkHQzgBsaigCxC9BCUohAQsgBSABNgIAIAEhBCAGQQFqIgYgACgCDEgNAAsMAgsgACgCuAEEQCAFIAAoAsyNAUEJSjYCAEEAIQIMAgtBACECIAVBADYCAAwBCyADIAMoAswBIgBBBGo2AswBIAAoAgAiAEUEQEF/IQIMAQsgAyAANgJgIARBn84AIANB4ABqEAohAgsgA0HQAWokACACC+MBAQV/QX8hCAJAIAAoApABIgVBkANtIgkgAkoNACACIQYgACgCnAEiB0GIJ0cEQCAHQYknayIGQQhLDQEgB0GNJ0wEfyAJIAZ0BSAHQYsnayAFbEEybQsiBiACSg0BCwJAIAZBkANsIAVGDQAgBkHIAWwgBUYNACAGQeQAbCAFRg0AIAZBMmwiByAFRg0AIAZBGWwgBUYNACAHIAVBA2xGDQAgByAFQQJ0Rg0AIAcgBUEFbEYNACAHIAVBBmxHDQELIAYhCAsgACABIAggAyAEQRggASACQX4gACgCcEEJQQEQaguRBQEFfwJAAkACQAJAAkAgAkEBTgRAIAJBAXEhCQJAIAJBAWsiCkUEQAwBCyACQX5xIQgDQCABIAdBAnRqIAAgAyAHaiAGbCAEakECdGoqAgBDAAAAR5Q4AgAgASAHQQFyIgtBAnRqIAAgAyALaiAGbCAEakECdGoqAgBDAAAAR5Q4AgAgB0ECaiEHIAhBAmsiCA0ACwsgCQRAIAEgB0ECdGogACADIAdqIAZsIARqQQJ0aioCAEMAAABHlDgCAAsgBUF/TA0DIAJBAUgNBSACQQFxIQggCg0BQQAhBwwCCyAFQX5HDQQgBkEBSg0DDAQLIAJBfnEhBEEAIQcDQCABIAdBAnRqIgIgAioCACAAIAMgB2ogBmwgBWpBAnRqKgIAQwAAAEeUkjgCACABIAdBAXIiAkECdGoiCSAJKgIAIAAgAiADaiAGbCAFakECdGoqAgBDAAAAR5SSOAIAIAdBAmohByAEQQJrIgQNAAsLIAhFDQIgASAHQQJ0aiIBIAEqAgAgACADIAdqIAZsIAVqQQJ0aioCAEMAAABHlJI4AgAPCyAFQX5HDQEgBkECSA0BCyACQX5xIQUgAkEBcSEJQQEhBANAAkAgAkEBSA0AQQAhByAFIQggAkEBRwRAA0AgASAHQQJ0aiIKIAoqAgAgACADIAdqIAZsIARqQQJ0aioCAEMAAABHlJI4AgAgASAHQQFyIgpBAnRqIgsgCyoCACAAIAMgCmogBmwgBGpBAnRqKgIAQwAAAEeUkjgCACAHQQJqIQcgCEECayIIDQALCyAJRQ0AIAEgB0ECdGoiCCAIKgIAIAAgAyAHaiAGbCAEakECdGoqAgBDAAAAR5SSOAIACyAEQQFqIgQgBkcNAAsLC8cJAQp/IwBBEGsiCCQAAkACQAJAAkAgAEH//ABMBEAgAEHAPkYNASAAQeDdAEYNAQwCCyAAQYD9AEYNACAAQYD3AkYNACAAQcC7AUcNAQsgAUEBa0EBSw0AIAJBgBBrIgdBA0sNACAHQQJHDQELQQAhByADRQ0BIANBfzYCAAwBCwJ/IAhB+J0BNgIMQQALBH9BAAUgCCAIKAIMQQNqQXxxNgIMIAgoAgwgAUHEtgEoAgBBAnRByLYBKAIAQQR0akGAIGpsakHQjwFqCxAdIgdFBEBBACEHIANFDQEgA0F5NgIADAELIwBBIGsiBiQAQX8hCQJAAkAgAEH//ABMBEAgAEHAPkYNASAAQeDdAEYNAQwCCyAAQYD9AEYNACAAQYD3AkYNACAAQcC7AUcNAQsgAUEBa0EBSw0AIAJBgBBrIgRBA0sNACAEQQJGDQAgB0EAAn8gBkH4nQE2AhxBAAsEf0EABSAGIAYoAhxBA2pBfHE2AhwgBigCHCABQcS2ASgCAEECdEHItgEoAgBBBHRqQYAgamxqQdCPAWoLEAkhBCAGQfidATYCHCAGIAYoAhxBA2pBfHEiBTYCHCAEIAE2AvBuIAQgATYCcCAEQdyNATYCBCAEQQA2ArQBIAQgADYCkAEgBCAFQdyNAWoiBTYCAEF9IQkgBEHcjQFqQQAgBEEIahBBDQAgBCABNgIIIAQgATYCDCAEQQA2AkwgBEIANwI4IARCCTcCLCAEQqjDATcCJCAEQoD9gIDAAjcCHCAEQoD9gICA6Ac3AhQgBCAEKAKQATYCECAEIAVqIgshBSAEKAK0ASEMQX8hCgJAIAFBAksNAEF5IQogBUUNAEEAIQogBUEAQcS2ASgCAEECdEHItgEoAgBBBHRqQYAgaiABbEH0AWoQCSIFQgE3AhwgBSABNgIIIAUgATYCBCAFQcC2ATYCAEHMtgEoAgAhDSAFQQU2AhggBUEYNgI8IAVCgICAgBA3AgwgBUL/////DzcCKCAFIAw2AkggBUKBgICAEDcCMCAFIA02AiQgBUG8H0EAEAoaIAUCfwJAAkACQCAAQf/8AEwEQCAAQcA+Rg0BIABB4N0ARw0CQQQMBAsgAEGA/QBGDQJBASAAQYD3AkYNAxogAEHAuwFHDQFBAgwDC0EGDAILQd/TAEH5NUHUABAGAAtBAws2AhwLIAoNAEEAIQkgBkEANgIQIAtBoM4AIAZBEGoQChogBiAEKAIsNgIAIAtBqh8gBhAKGiAEQoGAgIAQNwKUASAEIAAgAWxBuBdqNgKgASAEQpj4//+figE3AoABIAQgAjYCbCAEQph4NwKIASAEQpj4//+Pg383AnggBEKY+P//jwM3AqQBIARBiCc2ApwBIARBgICA/AM2AvxuIARBgIABOwH0biAEIAQoApABIgBB5ABtNgKsASAEIABB+gFtNgJ0QTwQCyEAIARBATYCrG8gBEHRCDYCoG8gBEHpBzYCkG8gBCAAQQh0NgL4biAEQbwBaiIAIAQoApABNgIIIABBADYCACAAQQxqQQBBqO0AEAkaIAQgBCgCbDYCwAELIAZBIGokACAJIQAgAwRAIAMgADYCAAsgAEUNACAHEAxBACEHCyAIQRBqJAAgBwsL1oYDSABBgAgL1xUDAAAABAAAAAQAAAAGAAAAg/miAERObgD8KRUA0VcnAN009QBi28AAPJmVAEGQQwBjUf4Au96rALdhxQA6biQA0k1CAEkG4AAJ6i4AHJLRAOsd/gApsRwA6D6nAPU1ggBEuy4AnOmEALQmcABBfl8A1pE5AFODOQCc9DkAi1+EACj5vQD4HzsA3v+XAA+YBQARL+8AClqLAG0fbQDPfjYACcsnAEZPtwCeZj8ALepfALondQDl68cAPXvxAPc5BwCSUooA+2vqAB+xXwAIXY0AMANWAHv8RgDwq2sAILzPADb0mgDjqR0AXmGRAAgb5gCFmWUAoBRfAI1AaACA2P8AJ3NNAAYGMQDKVhUAyahzAHviYABrjMAAGcRHAM1nwwAJ6NwAWYMqAIt2xACmHJYARK/dABlX0QClPgUABQf/ADN+PwDCMugAmE/eALt9MgAmPcMAHmvvAJ/4XgA1HzoAf/LKAPGHHQB8kCEAaiR8ANVu+gAwLXcAFTtDALUUxgDDGZ0ArcTCACxNQQAMAF0Ahn1GAONxLQCbxpoAM2IAALTSfAC0p5cAN1XVANc+9gCjEBgATXb8AGSdKgBw16sAY3z4AHqwVwAXFecAwElWADvW2QCnhDgAJCPLANaKdwBaVCMAAB+5APEKGwAZzt8AnzH/AGYeagCZV2EArPtHAH5/2AAiZbcAMuiJAOa/YADvxM0AbDYJAF0/1AAW3tcAWDveAN6bkgDSIigAKIboAOJYTQDGyjIACOMWAOB9ywAXwFAA8x2nABjgWwAuEzQAgxJiAINIAQD1jlsArbB/AB7p8gBISkMAEGfTAKrd2ACuX0IAamHOAAoopADTmbQABqbyAFx3fwCjwoMAYTyIAIpzeACvjFoAb9e9AC2mYwD0v8sAjYHvACbBZwBVykUAytk2ACio0gDCYY0AEsl3AAQmFAASRpsAxFnEAMjFRABNspEAABfzANRDrQApSeUA/dUQAAC+/AAelMwAcM7uABM+9QDs8YAAs+fDAMf4KACTBZQAwXE+AC4JswALRfMAiBKcAKsgewAutZ8AR5LCAHsyLwAMVW0AcqeQAGvnHwAxy5YAeRZKAEF54gD034kA6JSXAOLmhACZMZcAiO1rAF9fNgC7/Q4ASJq0AGekbABxckIAjV0yAJ8VuAC85QkAjTElAPd0OQAwBRwADQwBAEsIaAAs7lgAR6qQAHTnAgC91iQA932mAG5IcgCfFu8AjpSmALSR9gDRU1EAzwryACCYMwD1S34AsmNoAN0+XwBAXQMAhYl/AFVSKQA3ZMAAbdgQADJIMgBbTHUATnHUAEVUbgALCcEAKvVpABRm1QAnB50AXQRQALQ72wDqdsUAh/kXAElrfQAdJ7oAlmkpAMbMrACtFFQAkOJqAIjZiQAsclAABKS+AHcHlADzMHAAAPwnAOpxqABmwkkAZOA9AJfdgwCjP5cAQ5T9AA2GjAAxQd4AkjmdAN1wjAAXt+cACN87ABU3KwBcgKAAWoCTABARkgAP6NgAbICvANv/SwA4kA8AWRh2AGKlFQBhy7sAx4m5ABBAvQDS8gQASXUnAOu29gDbIrsAChSqAIkmLwBkg3YACTszAA6UGgBROqoAHaPCAK/trgBcJhIAbcJNAC16nADAVpcAAz+DAAnw9gArQIwAbTGZADm0BwAMIBUA2MNbAPWSxADGrUsATsqlAKc3zQDmqTYAq5KUAN1CaAAZY94AdozvAGiLUgD82zcArqGrAN8VMQAArqEADPvaAGRNZgDtBbcAKWUwAFdWvwBH/zoAavm5AHW+8wAok98Aq4AwAGaM9gAEyxUA+iIGANnkHQA9s6QAVxuPADbNCQBOQukAE76kADMjtQDwqhoAT2WoANLBpQALPw8AW3jNACP5dgB7iwQAiRdyAMamUwBvbuIA7+sAAJtKWADE2rcAqma6AHbPzwDRAh0AsfEtAIyZwQDDrXcAhkjaAPddoADGgPQArPAvAN3smgA/XLwA0N5tAJDHHwAq27YAoyU6AACvmgCtU5MAtlcEACkttABLgH4A2genAHaqDgB7WaEAFhIqANy3LQD65f0Aidv+AIm+/QDkdmwABqn8AD6AcACFbhUA/Yf/ACg+BwBhZzMAKhiGAE296gCz568Aj21uAJVnOQAxv1sAhNdIADDfFgDHLUMAJWE1AMlwzgAwy7gAv2z9AKQAogAFbOQAWt2gACFvRwBiEtIAuVyEAHBhSQBrVuAAmVIBAFBVNwAe1bcAM/HEABNuXwBdMOQAhS6pAB2ywwChMjYACLekAOqx1AAW9yEAj2nkACf/dwAMA4AAjUAtAE/NoAAgpZkAs6LTAC9dCgC0+UIAEdrLAH2+0ACb28EAqxe9AMqigQAIalwALlUXACcAVQB/FPAA4QeGABQLZACWQY0Ah77eANr9KgBrJbYAe4k0AAXz/gC5v54AaGpPAEoqqABPxFoALfi8ANdamAD0x5UADU2NACA6pgCkV18AFD+xAIA4lQDMIAEAcd2GAMnetgC/YPUATWURAAEHawCMsKwAssDQAFFVSAAe+w4AlXLDAKMGOwDAQDUABtx7AOBFzABOKfoA1srIAOjzQQB8ZN4Am2TYANm+MQCkl8MAd1jUAGnjxQDw2hMAujo8AEYYRgBVdV8A0r31AG6SxgCsLl0ADkTtABw+QgBhxIcAKf3pAOfW8wAifMoAb5E1AAjgxQD/140AbmriALD9xgCTCMEAfF10AGutsgDNbp0APnJ7AMYRagD3z6kAKXPfALXJugC3AFEA4rINAHS6JADlfWAAdNiKAA0VLACBGAwAfmaUAAEpFgCfenYA/f2+AFZF7wDZfjYA7NkTAIu6uQDEl/wAMagnAPFuwwCUxTYA2KhWALSotQDPzA4AEoktAG9XNAAsVokAmc7jANYguQBrXqoAPiqcABFfzAD9C0oA4fT7AI47bQDihiwA6dSEAPy0qQDv7tEALjXJAC85YQA4IUQAG9nIAIH8CgD7SmoALxzYAFO0hABOmYwAVCLMACpV3ADAxtYACxmWABpwuABplWQAJlpgAD9S7gB/EQ8A9LURAPzL9QA0vC0ANLzuAOhdzADdXmAAZ46bAJIz7wDJF7gAYVibAOFXvABRg8YA2D4QAN1xSAAtHN0ArxihACEsRgBZ89cA2XqYAJ5UwABPhvoAVgb8AOV5rgCJIjYAOK0iAGeT3ABV6KoAgiY4AMrnmwBRDaQAmTOxAKnXDgBpBUgAZbLwAH+IpwCITJcA+dE2ACGSswB7gkoAmM8hAECf3ADcR1UA4XQ6AGfrQgD+nd8AXtRfAHtnpAC6rHoAVfaiACuIIwBBulUAWW4IACEqhgA5R4MAiePmAOWe1ABJ+0AA/1bpABwPygDFWYoAlPorANPBxQAPxc8A21quAEfFhgCFQ2IAIYY7ACx5lAAQYYcAKkx7AIAsGgBDvxIAiCaQAHg8iQCoxOQA5dt7AMQ6wgAm9OoA92eKAA2SvwBloysAPZOxAL18CwCkUdwAJ91jAGnh3QCalBkAqCmVAGjOKAAJ7bQARJ8gAE6YygBwgmMAfnwjAA+5MgCn9Y4AFFbnACHxCAC1nSoAb35NAKUZUQC1+asAgt/WAJbdYQAWNgIAxDqfAIOioQBy7W0AOY16AIK4qQBrMlwARidbAAA07QDSAHcA/PRVAAFZTQDgcYAAQeMdC40JQPsh+T8AAAAALUR0PgAAAICYRvg8AAAAYFHMeDsAAACAgxvwOQAAAEAgJXo4AAAAgCKC4zYAAAAAHfNpNQgAAAAEAAAA4XpUP/YoXD/8DwAAEAAAAAQAAACamVk/rkdhP/wPAAAgAAAABAAAAMHKYT/D9Wg//A8AADAAAAAIAAAAuB5lP4PAaj8EEAAAQAAAAAgAAACoxms/16NwPwQQAABQAAAAEAAAADEIbD/Xo3A/DBAAAGAAAAAQAAAA16NwP4XrcT8MEAAAgAAAABAAAAAzM3M/MzNzPwwQAACgAAAAEAAAAI/CdT+PwnU/DBAAAMAAAAAgAAAA2c53P9nOdz8UEAAAAAEAACAAAACamXk/mpl5PxQQAAAgEAAAIAAAAEARAAAgAAAAYBIAACAAAACAEwAAQAAAAAAAAAAlkeC6IOrvPwAAAAAAAPA/JZHguiDq7z/eSyvPzajvP1of/5rmPO8/Vc8Xtdqn7j++oGT2ouvtP9eQbjq4Cu0/i+jPZQcI7D+13m+04+bqP1gAdBT3quk/InJVNDFY6D9Qxa5ptfLmP1jktgHIfuU/lEUnbLsA5D9HK0pL3XziP6mj42pk9+A/qqmXpb7o3j8WxHqCSO/bP0tmzI+FCdk/P+nhV+491j/Cam59P5LTP6C+p2ppC9E/K3JfOQhbzT8nmWIvkPfIP6EHyq8X8cQ/ymKsgIxKwT8ixb5sVAq8P2GFAIUfQbY/j95wH7k1sT9DhMmeTsOpPyF7e98ReKI/80co6LznmD9Z7Q7n6XWOPyECDqFKzX4/AAAAAAAAAADBU0zOHuLvPwAAAAAAAPA/wVNMzh7i7z/PQsiaDYnvPwxt55h/9u4/iBIteTwt7j+aTfS3DDHtP7WwwLqeBuw/zJkOGWaz6j/ceSzHdT3pP1GrIrtWq+c/lTbJTdwD5j91q+ek903kP3cAm96LkOI/E4HqH0TS4D/GAMPR2TLeP1M+BFWj19o/2QhhwT+d1z+oagbhn4zUP24kfRgprdE/Wu959kMJzj8bAGArVy7JP1GWaxuQzsQ/i+xardnrwD/p1ilefgq7P98X+tRvLrU/Bg2BTAA4sD/KvUTl9C+oP6YV+O2YeKE/S/VT0nlDmD+Uz5/0jQGQPwBuNz3/qIM/3mkZRs2ZdT/ghYzL4ShjP/yp8dJNYkA/AAAAAAAAAAC5pqOQItrvPwAAAAAAAPA/uaajkCLa7z+FCxbae2nvP0RGzXjXsO4/JlPDhsC07T8z2i5dVnvsP6nOFzkTDOs/qepxIYdv6T9y5pEeCq/nP9bRacRp1OU/wKekFJXp4z85oADlSvjhP+qDG9/NCeA/VWrVMkJN3D9DXd77n6zYPw9a9sGFPtU/HwXbykMN0j+gZzcjGEHOP4yLevPh+sg/8K5IhvtMxD904ycfzDfAP+5his0ib7k/O05VygCKsz/oYS7K6FetPyQzzSoieaU/u2lt+cyCnj8iLHRvj++UPz4R3RbZjIs/XcJfm6YygT9QCLLYBQd0P4HIKr4EG2U/3O6rk6/bUj8bypqibUY3PwBBgCcL4THIUQzShPTvPwAAAAAAAPA/yFEM0oT07z/2lQfpKdLvP9rTxPEyme8/1P0Q2Q9K7z9+n7tuW+XuP2HBP53Za+4/HdfxJXXe7T9qf2/sPD7tP8nqNcFgjOw/dyRFAS7K6z8evH7aC/nqPzrQvzR3Guo/9SUjgP4v6T/yQEODPTvoPw4HU97YPec/9/Kvo3k55j9MyMUgyS/lP864eJFsIuQ//5laGQET4z8vnDHtFwPiP2PZBs0y9OA/TVqGcoHP3z/Nj2T7Nb7dPxXGN5AFt9s/4AetqD282T9gMwqT88/XP/Md/MQB9NU/SoVn+AUq1D/nzTwUYHPSP43KNDcy0dA/2NF68MGIzj+vJ3gSKpvLP8hIk9552sg/tc9bIx9Hxj89V0IUH+HDP7XNAUAdqME/TbqQu8Y2vz8uDCY41HO7P2aSBQrEBLg/gFQWx3nmtD9iSE4mbhWyP6QVhJeFG68/7LLrIKeWqj+XqEFFk5OmPz54L+9YCaM/1eesR8jdnz9sz00XOXaaP/Tx2Oj/yZU/Dwu1pnnHkT9VF2z6HruMP/6ksSiy94Y/PLeW6n4lgj+l+7XMVE58P2cfVHefwnU/BcR/FTt1cD90f7OcnW9oP9Pw8wCSwGE/91Lb+qcjWT8/wazteUBRP/FCAJH6wkY/e7LNUz6APD8mUZIi8I8wP8dUbmB6FCE/fYl/NyCrCz/xaOOItfjkPgAAAAAAAAAAqMsAAGFzc2VydGlvbiBmYWlsZWQ6IGVuY0NvbnRyb2wtPm5DaGFubmVsc0ludGVybmFsID09IDEgfHwgcHNFbmMtPnN0YXRlX0Z4eFsgMCBdLnNDbW4uZnNfa0h6ID09IHBzRW5jLT5zdGF0ZV9GeHhbIDEgXS5zQ21uLmZzX2tIegBhc3NlcnRpb24gZmFpbGVkOiBpbkxlbiA+PSBTLT5Gc19pbl9rSHoAYXNzZXJ0aW9uIGZhaWxlZDogUy0+aW5wdXREZWxheSA8PSBTLT5Gc19pbl9rSHoAYXNzZXJ0aW9uIGZhaWxlZDogdGFyZ2V0X3B0ciArIHNmX2xlbmd0aF84a0h6IDw9IGZyYW1lXzRrSHogKyBmcmFtZV9sZW5ndGhfNGtIegAtKyAgIDBYMHgAYXNzZXJ0aW9uIGZhaWxlZDogY29kZWRCYW5kcyA+IHN0YXJ0AGFzc2VydGlvbiBmYWlsZWQ6ICFyZXQAYXNzZXJ0aW9uIGZhaWxlZDogTj4xCmFsZ191bnF1YW50KCkgbmVlZHMgYXQgbGVhc3QgdHdvIGRpbWVuc2lvbnMAYXNzZXJ0aW9uIGZhaWxlZDogTj4xCmFsZ19xdWFudCgpIG5lZWRzIGF0IGxlYXN0IHR3byBkaW1lbnNpb25zAGFzc2VydGlvbiBmYWlsZWQ6IHBzRW5jQy0+cHNOTFNGX0NCLT5vcmRlciA9PSBwc0VuY0MtPnByZWRpY3RMUENPcmRlcgBhc3NlcnRpb24gZmFpbGVkOiBkIDw9IGxlbgBhc3NlcnRpb24gZmFpbGVkOiBidWZfbGVuID49IHBzRW5jLT5zQ21uLnBpdGNoX0xQQ193aW5fbGVuZ3RoAGFzc2VydGlvbiBmYWlsZWQ6IE1BWF9GUkFNRV9MRU5HVEggPj0gcHNFbmNDLT5mcmFtZV9sZW5ndGgAYXNzZXJ0aW9uIGZhaWxlZDogKCBwc0VuYy0+c0Ntbi5zdWJmcl9sZW5ndGggKiBwc0VuYy0+c0Ntbi5uYl9zdWJmciApID09IHBzRW5jLT5zQ21uLmZyYW1lX2xlbmd0aABhc3NlcnRpb24gZmFpbGVkOiBlbmNDb250cm9sLT5uQ2hhbm5lbHNJbnRlcm5hbCA9PSAxIHx8IHBzRW5jLT5zdGF0ZV9GeHhbIDEgXS5zQ21uLmlucHV0QnVmSXggPT0gcHNFbmMtPnN0YXRlX0Z4eFsgMSBdLnNDbW4uZnJhbWVfbGVuZ3RoAGFzc2VydGlvbiBmYWlsZWQ6IHBzRW5jLT5zdGF0ZV9GeHhbIDAgXS5zQ21uLmlucHV0QnVmSXggPT0gcHNFbmMtPnN0YXRlX0Z4eFsgMCBdLnNDbW4uZnJhbWVfbGVuZ3RoAGFzc2VydGlvbiBmYWlsZWQ6IE9yZGVyIDw9IGxlbmd0aAAuL2NlbHQvcGl0Y2guaABhc3NlcnRpb24gZmFpbGVkOiBfdGhpcy0+b2ZmcytfdGhpcy0+ZW5kX29mZnM8PV9zaXplAGFzc2VydGlvbiBmYWlsZWQ6IEs+MAphbGdfdW5xdWFudCgpIG5lZWRzIGF0IGxlYXN0IG9uZSBwdWxzZQBhc3NlcnRpb24gZmFpbGVkOiBLPjAKYWxnX3F1YW50KCkgbmVlZHMgYXQgbGVhc3Qgb25lIHB1bHNlAGFzc2VydGlvbiBmYWlsZWQ6IGZpbiAhPSBmb3V0CkluLXBsYWNlIEZGVCBub3Qgc3VwcG9ydGVkAHNpbGsvY2hlY2tfY29udHJvbF9pbnB1dC5jAHNpbGsvc29ydC5jAGNlbHQvY2VsdC5jAGNlbHQva2lzc19mZnQuYwBjZWx0L2N3cnMuYwBzaWxrL2VuY29kZV9wdWxzZXMuYwBzaWxrL2VuY29kZV9pbmRpY2VzLmMAY2VsdC9iYW5kcy5jAHNpbGsvcHJvY2Vzc19OTFNGcy5jAHNpbGsvTFBDX2FuYWx5c2lzX2ZpbHRlci5jAHNpbGsvcmVzYW1wbGVyLmMAY2VsdC9jZWx0X2VuY29kZXIuYwBzcmMvb3B1c19lbmNvZGVyLmMAY2VsdC92cS5jAGNlbHQvcGl0Y2guYwBjZWx0L3JhdGUuYwBzaWxrL2ludGVycG9sYXRlLmMAc2lsay9OTFNGX2VuY29kZS5jAGNlbHQvbGFwbGFjZS5jAHNpbGsvc3RlcmVvX2VuY29kZV9wcmVkLmMAY2VsdC9jZWx0X2xwYy5jAGNlbHQvZW50ZW5jLmMAY2VsdC9lbnRkZWMuYwBzaWxrL2NvbnRyb2xfY29kZWMuYwBzaWxrL05TUV9kZWxfZGVjLmMAc2lsay9OTFNGX1ZRX3dlaWdodHNfbGFyb2lhLmMAc2lsay9yZXNhbXBsZXJfcHJpdmF0ZV9kb3duX0ZJUi5jAHNpbGsvTkxTRl9WUS5jAHNpbGsvTlNRLmMAc2lsay9mbG9hdC9hcHBseV9zaW5lX3dpbmRvd19GTFAuYwBzaWxrL2Zsb2F0L3NvcnRfRkxQLmMAc2lsay9mbG9hdC9maW5kX3BpdGNoX2xhZ3NfRkxQLmMAc2lsay9mbG9hdC9maW5kX3ByZWRfY29lZnNfRkxQLmMAc2lsay9mbG9hdC9zY2h1cl9GTFAuYwBzaWxrL2Zsb2F0L0xQQ19hbmFseXNpc19maWx0ZXJfRkxQLmMAc2lsay9mbG9hdC93YXJwZWRfYXV0b2NvcnJlbGF0aW9uX0ZMUC5jAHNpbGsvZmxvYXQvcGl0Y2hfYW5hbHlzaXNfY29yZV9GTFAuYwBzaWxrL2Zsb2F0L2VuY29kZV9mcmFtZV9GTFAuYwBzaWxrL2Zsb2F0L2J1cmdfbW9kaWZpZWRfRkxQLmMAc2lsay9mbG9hdC9maW5kX0xQQ19GTFAuYwBzaWxrL2VuY19BUEkuYwBzaWxrL1ZBRC5jAHNpbGsvTkxTRjJBLmMAYXNzZXJ0aW9uIGZhaWxlZDogQyplYml0c1tqXTw8QklUUkVTID09IGJpdHNbal0AYXNzZXJ0aW9uIGZhaWxlZDogY29tcGxleGl0eSA8PSBTSUxLX1BFX01BWF9DT01QTEVYAGFzc2VydGlvbiBmYWlsZWQ6IGNvbXBsZXhpdHkgPj0gU0lMS19QRV9NSU5fQ09NUExFWABhc3NlcnRpb24gZmFpbGVkOiBfbmJpdHM8PUVDX1NZTV9CSVRTAGFzc2VydGlvbiBmYWlsZWQ6IGl4WyBuIF1bIDEgXSA8IFNURVJFT19RVUFOVF9TVUJfU1RFUFMAYXNzZXJ0aW9uIGZhaWxlZDogcHNFbmNDLT5wcmVkaWN0TFBDT3JkZXIgPD0gTUFYX0xQQ19PUkRFUgBhc3NlcnRpb24gZmFpbGVkOiBwc0VuY0MtPnBpdGNoRXN0aW1hdGlvbkxQQ09yZGVyIDw9IE1BWF9GSU5EX1BJVENIX0xQQ19PUkRFUgBhc3NlcnRpb24gZmFpbGVkOiBlbmNDb250cm9sICE9IE5VTEwAYXNzZXJ0aW9uIGZhaWxlZDogTCA+PSBLAGFzc2VydGlvbiBmYWlsZWQ6IHN1YmZyX2xlbmd0aCAqIG5iX3N1YmZyIDw9IE1BWF9GUkFNRV9TSVpFAGFzc2VydGlvbiBmYWlsZWQ6IHN0LT5tb2RlID09IE1PREVfSFlCUklEIHx8IGN1cnJfYmFuZHdpZHRoID09IE9QVVNfQkFORFdJRFRIX1dJREVCQU5EAGFzc2VydGlvbiBmYWlsZWQ6IGxhZyA+IDAgfHwgc2lnbmFsVHlwZSAhPSBUWVBFX1ZPSUNFRABhc3NlcnRpb24gZmFpbGVkOiBvcmRlciA+PSAwICYmIG9yZGVyIDw9IFNJTEtfTUFYX09SREVSX0xQQwBhc3NlcnRpb24gZmFpbGVkOiBmbCtmczw9MzI3NjgAYXNzZXJ0aW9uIGZhaWxlZDogRnNfa0h6ID09IDgAYXNzZXJ0aW9uIGZhaWxlZDogcW4gPD0gMjU2AGFzc2VydGlvbiBmYWlsZWQ6IGQ9PTEwIHx8IGQ9PTE2AGFzc2VydGlvbiBmYWlsZWQ6IGZzX2tIeiA9PSA4IHx8IGZzX2tIeiA9PSAxMiB8fCBmc19rSHogPT0gMTYAYXNzZXJ0aW9uIGZhaWxlZDogRnNfa0h6ID09IDggfHwgRnNfa0h6ID09IDEyIHx8IEZzX2tIeiA9PSAxNgBhc3NlcnRpb24gZmFpbGVkOiBkID49IDYAYXNzZXJ0aW9uIGZhaWxlZDogdHlwZU9mZnNldCA+PSAwICYmIHR5cGVPZmZzZXQgPCA2AGFzc2VydGlvbiBmYWlsZWQ6IHBzUmFuZ2VFbmMtPm9mZnMgPD0gMTI3NQBhc3NlcnRpb24gZmFpbGVkOiBzUmFuZ2VFbmNfY29weTIub2ZmcyA8PSAxMjc1AGFzc2VydGlvbiBmYWlsZWQ6IG4gPCAyNQBhc3NlcnRpb24gZmFpbGVkOiBtPT00AGFzc2VydGlvbiBmYWlsZWQ6IHBzRW5jLT5zQ21uLm5iX3N1YmZyID09IDIgfHwgcHNFbmMtPnNDbW4ubmJfc3ViZnIgPT0gNABhc3NlcnRpb24gZmFpbGVkOiBpZmFjdF9RMiA8PSA0AGFzc2VydGlvbiBmYWlsZWQ6IGxlbj49MwBhc3NlcnRpb24gZmFpbGVkOiBpeFsgbiBdWyAwIF0gPCAzAGFzc2VydGlvbiBmYWlsZWQ6IF9uPj0yAGFzc2VydGlvbiBmYWlsZWQ6IGVuY29kZV9MQlJSID09IDAgfHwgdHlwZU9mZnNldCA+PSAyAGFzc2VydGlvbiBmYWlsZWQ6IHdpbl90eXBlID09IDEgfHwgd2luX3R5cGUgPT0gMgBhc3NlcnRpb24gZmFpbGVkOiBzaWduYWxUeXBlID49IDAgJiYgc2lnbmFsVHlwZSA8PSAyAGFzc2VydGlvbiBmYWlsZWQ6IHBzRW5jLT5zQ21uLmx0cF9tZW1fbGVuZ3RoIC0gcHNFbmMtPnNDbW4ucHJlZGljdExQQ09yZGVyID49IHBzRW5jQ3RybC0+cGl0Y2hMWyAwIF0gKyBMVFBfT1JERVIgLyAyAGFzc2VydGlvbiBmYWlsZWQ6IF9mdD4xAGFzc2VydGlvbiBmYWlsZWQ6IF9uPjEAYXNzZXJ0aW9uIGZhaWxlZDogbmJfc3ViZnIgPT0gUEVfTUFYX05CX1NVQkZSID4+IDEAYXNzZXJ0aW9uIGZhaWxlZDogZW5jQ29udHJvbC0+bkNoYW5uZWxzQVBJID09IDEgJiYgZW5jQ29udHJvbC0+bkNoYW5uZWxzSW50ZXJuYWwgPT0gMQBhc3NlcnRpb24gZmFpbGVkOiBjb3VudD4wAGFzc2VydGlvbiBmYWlsZWQ6IF9iaXRzPjAAYXNzZXJ0aW9uIGZhaWxlZDogZnM+MABhc3NlcnRpb24gZmFpbGVkOiBuYkJhbmRzPjAAYXNzZXJ0aW9uIGZhaWxlZDogbGVuPjAAYXNzZXJ0aW9uIGZhaWxlZDogbj4wAGFzc2VydGlvbiBmYWlsZWQ6IF9rPjAAYXNzZXJ0aW9uIGZhaWxlZDogbWF4X3BpdGNoPjAAYXNzZXJ0aW9uIGZhaWxlZDogc3RyaWRlPjAAYXNzZXJ0aW9uIGZhaWxlZDogZW5kPjAAYXNzZXJ0aW9uIGZhaWxlZDogb3ZlcmxhcD49MABhc3NlcnRpb24gZmFpbGVkOiBzdW0+PTAAYXNzZXJ0aW9uIGZhaWxlZDogaXRoZXRhPj0wAGFzc2VydGlvbiBmYWlsZWQ6IHN0LT5zaWduYWxsaW5nPT0wAGFzc2VydGlvbiBmYWlsZWQ6IENvbXBsZXhpdHkgPj0gMCAmJiBDb21wbGV4aXR5IDw9IDEwAGFzc2VydGlvbiBmYWlsZWQ6IGZyYW1lX2xlbmd0aCA9PSAxMiAqIDEwAGFzc2VydGlvbiBmYWlsZWQ6IHN0LT5zaWxrX21vZGUuaW50ZXJuYWxTYW1wbGVSYXRlID09IDE2MDAwAGFzc2VydGlvbiBmYWlsZWQ6IHN0YXJ0X2lkeCA+IDAAYXNzZXJ0aW9uIGZhaWxlZDogblN0YXRlc0RlbGF5ZWREZWNpc2lvbiA+IDAAYXNzZXJ0aW9uIGZhaWxlZDogbGVuZ3RoX2Rfc3JjaCA+IDAAYXNzZXJ0aW9uIGZhaWxlZDogTiA+IDAAYXNzZXJ0aW9uIGZhaWxlZDogTCA+IDAAYXNzZXJ0aW9uIGZhaWxlZDogSyA+IDAAYXNzZXJ0aW9uIGZhaWxlZDogRCA+IDAAYXNzZXJ0aW9uIGZhaWxlZDogTkxTRl9tdV9RMjAgPiAwAGFzc2VydGlvbiBmYWlsZWQ6ICpsYWdJbmRleCA+PSAwAGFzc2VydGlvbiBmYWlsZWQ6IGViaXRzW2pdID49IDAAYXNzZXJ0aW9uIGZhaWxlZDogYml0c1tqXSA+PSAwAGFzc2VydGlvbiBmYWlsZWQ6IGlmYWN0X1EyID49IDAAYXNzZXJ0aW9uIGZhaWxlZDogKGQgJiAxKSA9PSAwAGFzc2VydGlvbiBmYWlsZWQ6ICggbGVuZ3RoICYgMyApID09IDAAYXNzZXJ0aW9uIGZhaWxlZDogKCBMUENfb3JkZXIgJiAxICkgPT0gMABhc3NlcnRpb24gZmFpbGVkOiAoIG9yZGVyICYgMSApID09IDAAYXNzZXJ0aW9uIGZhaWxlZDogKCBzaGFwaW5nTFBDT3JkZXIgJiAxICkgPT0gMABhc3NlcnRpb24gZmFpbGVkOiAoIEQgJiAxICkgPT0gMABhc3NlcnRpb24gZmFpbGVkOiAwAGFzc2VydGlvbiBmYWlsZWQ6ICFjZWx0X2lzbmFuKG5vcm0pAChudWxsKQBhc3NlcnRpb24gZmFpbGVkOiAhY2VsdF9pc25hbih0bXBbMF0pAGFzc2VydGlvbiBmYWlsZWQ6ICFjZWx0X2lzbmFuKGZyZXFbMF0pICYmIChDPT0xIHx8ICFjZWx0X2lzbmFuKGZyZXFbTl0pKQBhc3NlcnRpb24gZmFpbGVkOiBwc0VuY0MtPmluZGljZXMuTkxTRkludGVycENvZWZfUTIgPT0gNCB8fCAoIHBzRW5jQy0+dXNlSW50ZXJwb2xhdGVkTkxTRnMgJiYgIXBzRW5jQy0+Zmlyc3RfZnJhbWVfYWZ0ZXJfcmVzZXQgJiYgcHNFbmNDLT5uYl9zdWJmciA9PSBNQVhfTkJfU1VCRlIgKQBhc3NlcnRpb24gZmFpbGVkOiBwc0VuY0MtPmZyYW1lX2xlbmd0aCA9PSA4ICogc2lsa19SU0hJRlQoIHBzRW5jQy0+ZnJhbWVfbGVuZ3RoLCAzICkAYXNzZXJ0aW9uIGZhaWxlZDogcHNFbmNDLT51c2VJbnRlcnBvbGF0ZWROTFNGcyA9PSAxIHx8IHBzRW5jQy0+aW5kaWNlcy5OTFNGSW50ZXJwQ29lZl9RMiA9PSAoIDEgPDwgMiApAEZhdGFsIChpbnRlcm5hbCkgZXJyb3IgaW4gJXMsIGxpbmUgJWQ6ICVzCgAAAAAAAAAAEQAKABEREQAAAAAFAAAAAAAACQAAAAALAAAAAAAAAAARAA8KERERAwoHAAEACQsLAAAJBgsAAAsABhEAAAAREREAQfHYAAshCwAAAAAAAAAAEQAKChEREQAKAAACAAkLAAAACQALAAALAEGr2QALAQwAQbfZAAsVDAAAAAAMAAAAAAkMAAAAAAAMAAAMAEHl2QALAQ4AQfHZAAsVDQAAAAQNAAAAAAkOAAAAAAAOAAAOAEGf2gALARAAQavaAAseDwAAAAAPAAAAAAkQAAAAAAAQAAAQAAASAAAAEhISAEHi2gALDhIAAAASEhIAAAAAAAAJAEGT2wALAQsAQZ/bAAsVCgAAAAAKAAAAAAkLAAAAAAALAAALAEHN2wALAQwAQdnbAAtHDAAAAAAMAAAAAAkMAAAAAAAMAAAMAAAwMTIzNDU2Nzg5QUJDREVGAACdPgBAXj4AwAQ+AIDtPgBAiT4AAAAAAMBMPwAAzT0AQbHcAAs0/wD/AP8A/wD/AP4BAAH/AP4A/QIAAf8A/gD9AwAB/wBAAABsIgAAQg8AABIGAABNAgAA2wBB8NwACxXtAAAAmQAAAEkAAAAeAAAADAAAAAcAQZHdAAsVQAAAk10AAL1wAADteQAAsn0AACR/AEGw3QALRDB1AABwFwAAINH//yDR///9+vTp1LaWg3huYlVIPDEoIBkTDw0LCQgHBgUEAwIBANLQzsvHwbeojmhKNCUbFA4KBgQCAEGA3gAL0gjfybenmIp8b2JYT0Y+ODIsJyMfGxgVEhAODAoIBgQDAgEAvLCbindhQysaCgCld1A9LyMbFA4JBABxPwAAAAAADCM8U2yEnbTO5A8gN01lfZevyeETKkJZcomiuNHmDBkySGF4k6zI3xosRVpyh5+0zeENFjVQaoKctM3kDxksQFpzjqjE3hMYPlJkeJGovtYWHzJPZ3iXqsvjFR0tQWp8lqvE4B4xS2F5jqW60eUTGTRGXXSPpsDbGiI+S2F2kafC2RkhOEZbcY+lxN8VIjNIYXWRq8TeFB0yQ1p1kKjF3RYfMEJfdZKoxN4YITNNdIaetMjgFRxGV2p8larC2RohNUBTdZitzOEbIkFfbIGbrtLhFBpIY3GDmrDI2yIrPU5dcpuxzeUXHTZhfIqjs9HlHiY4WXaBnrLI5xUdMT9Vb46jwd4bME1nhZ6zxNfoHS9KY3yXsMbc7SEqPUxdeZuuz+EdNVdwiJqqvNDjGB40VIOWprrL5SUwQFRodpyxyeZRCwoJCgkKCe8I7wgKCfwIFwnvCEgLFApaCT8JCgniCOII4gjiCJIItwkkCSQJCgkKCQoJJAkkCT8JMgmQDM4KJAkkCQoJ4gitCJ8I1QiSCJwJqgk/CVoJWglaCVoJPwlnCQoJlw3wC08InwjiCOII4gjvCAoJ1QjSDEUMFApaCccIrQifCJIIkghCCAAQBQ+tCDwKPApnCQoJWgk/CRoIagysDD8JrQj5CYIJJAkKCXcIrQgKDaANpgqSCNUInAkyCT8Jnwg1CDIJdAkXCT8JWgl0CXQJdAmcCT8Jww4tDoIJ3wk/CeII4gj8CJ8IAAi2DJkMmQoeC48JFwn8CPwI4ghPCL8M5AzBCvYKjwnVCNUIxwhPCDUIOQulC0kKPwlnCTIJkgjHCMcIQgiZDH0MSQoUCuIIhQjHCK0IrQhdCGoM7gy0CmcJ4gjiCOII7wiSCEIIRQzIDJwJDQjvCMQJPwm3CYIJhQizDdIMCgmMClcKqgk/CVoJJAlPCF8Nzw3eC/AL/AieB60I4gjiCOIITA0mDScIfwo5CzIJdAniCKoJ7AmwDqANngdkClEL3wlaCT8JnAnVCNQLyAy0CkgLtApqCE8I7wi6CMcIbw5JDukHsQdkCowKFArECRcJPwmHDFUNMgkaCEgLSAskCbcJxwh3CAoNJg0eC9wKFwlqCOII7whCCA0IFwn8CIUIdwiFCD8JSQqMCowK+QlnCYIJrQjVCK0IrQgkCXQJLwqMCt4LrAz2CkgLqgkaCPwICgkyCUwJrQhqCE8I7wjECekK6Qo8ChQKPwlcDoEOugguB4UIwQqmCnEK0QmfCOkKWAymCvkJHgvRCYUIWgmtCIUI1LKUgWxgVVJPTT07OTgzMTAtKikoJiQiHx4VDAoDAQD/9fTs6eHZy76wr6GViH1yZltRRzw0KyMcFBMSDAsFALOKjJSXlZmXo3RDUjtcSGRZXABB4OYAC+cBEAAAAABjQiQkIiQiIiIiU0UkNCJ0ZkZERLBmREQiQVVEVCR0jZiLqoS7uNiJhPmouYtoZmRERLLaubmq9Ni7u6r0u7vbimebuLmJdLebmIiE2bi4qqTZq5uL9Km4uaqk2N/aitaPvNqo9I2Im6qoitzbi6TbytiJqLr2uYt0udu5imRkhmRmIkREZESoy93aqKeaiGhGpPariYuJm9rbi//+/e4OAwIBAP/+/NojAwIBAP/++tA7BAIBAP/+9sJHCgIBAP/87LdSCAIBAP/867RaEQIBAP/44KthHgQBAP/+7K1fJQcBAEHQ6AAL7g3///+DBpH//////+xdD2D//////8JTGUfd/////6JJIkKi////0n5JKzmt////yX1HMDqC////pm5JOT5o0v//+3tBN0Rkq/8AAAAAAAAAAPoAAwAGAAMAAwADAAQAAwADAAMAzQEAACAACgAULmQBQC8AAIAwAAAAMwAAQDMAAGAzAAAANAAAUDQAAKA0AAAHFyY2RVVkdIOTorLB0N/vDRkpN0VTYnB/jp2ru8vc7A8VIjM9TlxqfoiYp7nN4fAKFSQyP09fbn6Nna29zd3tERQlMztOWWt7hpakuM3g8AoPIDNDUWBwgY6erb3M3OwIFSUzQU9icX6Km6izwNHaDA8iNz9OV2x2g5Snucvb7BATICQ4T1tsdoiaq7rM3O0LHCs6SllpeIeWpbTE0+LxBhAhLjxLXGt7iZypucfW4QsTHiw5SllpeYeYqbrK2uoMEx0uOUdYZHiElKW2x9jpERcjLjhNXGp7hpinucze7Q4RLTU/S1lrc4SXq7zO3fAJEB0oOEdYZ3eJmqu9zd7tEBMkMDlMV2l2hJanucra7AwRHTZHUV5ofoiVpLbJ3e0PHC8+T2FzgY6bqLTC0N/uCA4eLT5OXm9/j5+vwM/f7xEeMT5PXGt3hJGgrr7M3OsOEyQtPUxbbHmKmqy9zd7uDBIfLTxMW2t7ipqru8zd7A0RHys1RlNncoOVp7nL3O0RFiMqOk5dbn2Lm6q8zuDwCA8iMkNTY3ODkqKywdHg7w0QKUJJVl9vgImWo7fO4fERGSU0P0tcZneEkKCvv9TnEx8xQVNkdYWToa67yNXj8hIfNERYZ3V+ipWjscDP3+8QHS89TFpqd4WTobDB0eDwDxUjMj1JVmFud4GNr8ba7UkObQttC20LbQttC20LbQttC20LbQttC5MLkwttCx4LkAwNDJwL8AvwC8ILwgvCC5MLkwvCC5wLSAseCx4LpgpQD64PpQuHDIcMdgvwCx4LMgysDG0LHgs8CvkJ3AptC7wNfQzCCx8MywtIC20LbQttC20LSAtIC0gLSAtIC8EKvhO+E3YL9Q05DfALDQzpClgMWAycCx4L0QnsCcEKSAtMETUQjArBCpwLwgttCx4LpQvLC20LbQttC20LSAumCiQOywucC/AL8As5C/YK8AuQDOcLpQvbDNsMpQvuDK8LaxSWE+wJCg3GDTkNfQwWDDANpQuMClcKfwrpCh4LcQrZEzYUBxJMEZwJUQvnC4cMYQx/CrQKSAseC+kKHguMCjIMSAuTC20LbQttC20LkwuTC5MLkwttC20LkwuTC5MLahCHDKULHwzCC0gLSAttC5wLOQtkC8sLnAvCC30MOQuwDrAOrAwfDKULSAttC0gLnAt2C+kK6QoeC0gLSAtkCg4Prg+HDDIMrAx2C+cLkwuTCw0MHgvpCukK6QrpChQKBQ/wDx0NvA0WDLQKwgt2CzIMDQweCx4LVwpXCh4L9gobFB4TmQwFD3ENYQxRC1UNew2MChQKcQq0Ch4L9grBCg0QzQ7bDFgMbQtIC0gLbQvpCrQK6Qq0CukKHgtIC/YK2RO+E+cL2Q2sDPALDQyACx8MUQu0CrQKtAoeC+kKPArVENUQLAvfCYcMMA0wDQMMAwwwDfALHgtXChQKpgrBCvALZAv2CkgLtAp/ClELHwxODE4MkAxhDPALwguTCx4LFxEqD20LSAseC0gLHgseC0gLSAtICx4LSAttC0gLHgulC2QLZAulC6UL8AsyDJAMTgzwC8ILnAucC5wLbQu0CoUQNRDuDBMNbQuTC0gLpQulCx4L6Qq0Ch4LHgseC+kK8A+uDx8MwgttC20LbQtIC20LbQseCx4LHgvpCkgL3AoHEt8RYQxxDYcMpQtRC94LMgy0Cn8Kfwp/CrQK6QqMCjUQrRDNDkkOpgrcCkgLSAvCC5wLbQseC38KfwrpCkgLdxDiDcEKHgseC0gLSAtIC20LbQtIC20LbQttC5MLSAs2FDkT1QhoDc0Olw0TDR4L7gyXDU4MUQucCbcJwQptC3sNZQ4yDH0MHQ3nC4cMhwylC5AMDQxtC20LfwrsCYIJpQvCC+kK6Qq0CukKHgucC/ALHwxODE4MTgwfDMILwguACzkLfwqmCtwKwgtoDdkNHQ2sDPALwguTC20LSAseC8sLgAtRC8ILwgucC8sLHwzwC/ALwgtICx4LbQttC0gLUA9/D8ILfQwdDZAM2wzbDJcNeA5xDaYKhQicCRQKLwrhzMm4t6+empmHd3Nxbm1jYl9PRDQyMC0rIB8bEgoDAP/76+bUycS2p6ajl4p8bmhaTkxGRTktIhgVCwYFBAMAr5SgsLKtrqSxrsS2xsC2RD5CPEh1VVp2iJeOoI6bAEHH9gALwAIBZGZmREQkImCka565tLmLZkBCJCIiAAEg0IuNv5i5m2hgq2imZmZmhAEAAAAAEBAAUG1Oa7mLZ2XQ1I2LrZl7ZyQAAAAAAAABMAAAAAAAACBEh3t3d2dFYkRneHZ2ZkdihoiduLaZi4bQqPhLvY95ayAxIiIiABEC0uuLe7mJaYZih2i2ZLerhmRGREZCQiKDQKZmRCQCAQCGpmZEIiJChNT2notra1dmZNt9eol2Z4Ryh4lpq2oyIqTWjY+5l3lnwCIAAAAAAAHQbUq7hvmfiWZumnZXZXdlAAIAJCRCRCNgpGZkJAACIaeKrmZkVAICZGt4dyTFGAD//v30DAMCAQD//vzgJgMCAQD//vvROQQCAQD//vTDRQQCAQD/++i4VAcCAQD//vC6Vg4CAQD//u+yWx4FAQD/+OOxZBMCAQBBkPkAC8QF////nASa///////jZg9c///////VUxhI7P////+WTCE/1v///755TSs3uf////WJRys7i/////+DQjJCa8L//6Z0TDc1ff//AAAAAAAAAABkAAMAKAADAAMAAwAFAA4ADgAKAAsAAwAIAAkABwADAFsBAAAgABAAZiarAeA0AADgNgAA4DoAACA7AABAOwAAQDwAAJA8AADgPAAAAAAAAFzKvti235rinOZ47Hr0zPw0A4YLiBNkGWYdSiBCJ6Q1+ff29fTq0srJyMWuUjs4NzYuFgwLCgkHAEAAy5YA18OmfW5SAAAAAGs9AABuPQAAeACAQADongoA5gDz3cC1AGQA8AAgAGQAzTwAMAAgq1UAwIBAAM2aZjMA1auAVSsA4MCggGBAIABkKBAHAwEAAApn8g5WzeQdCmfyDnVSggxZmgQZdVKCDEYRMQrtA2IURhExCtoC1wf5xq0P2gLXByK2UgXa+qQKIrZSBQAAAABG8y4eK+NLDh9mgBgcLB0K2mFIEu2c9AbsMBML45ClBO2kHQIK32sDAAAAAAAAAAAqr9XJz/9AABEAY/9hARD+owAnK71W2f8GAFsAVv+6ABcAgPzAGNhN7f/c/2YAp//o/0gBSfwICiU+AAAAAAAAh8c9yUAAgACG/yQANgEA/UgCMyRFRQwAgAASAHL/IAGL/5/8GxB7OAAAAAAAAAAAaAINyPb/JwA6ANL/rP94ALgAxf7j/QQFBBVAIwAAAADmPsbE8/8AABQAGgAFAOH/1f/8/0EAWgAHAGP/CP/U/1ECLwY0CscMAAAAAAAAAADkVwXFAwDy/+z/8f8CABkAJQAZAPD/uf+V/7H/MgAkAW8C1gMIBbgFAAAAAAAAAACUa2fEEQAMAAgAAQD2/+r/4v/g/+r/AwAsAGQAqADzAD0BfQGtAccBE/WV5lkS8ykfBlQgAEHg/gALlwG9AKj9aQJnd3UAYf/S+wh0NADdAKj2dG78/xEC6vLlZtD/9gKM8KVdsP+JA3XvBlOd/8wDgu9mR5X/xwOL8Cc7mf+AA2Hyri6l/wUDz/ReIrn/YwKh95gW0v+pAaH6tAsGAAMABwMAAQoAAgYSCgwEAAIAAAAJBAcEAAMMBwcAAOBwLA8DAgEA/u3AhEYXBAD//OKbPQsCAEGAgAELM/r16stHMiomIyEfHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAs2MARzgrHhUMBgBBwIABC0THpZB8bWBURz0zKiAXDwgA8eHTx7uvpJmOhHtyaWBYUEhAOTIsJiEdGBQQDAkFAgAsQAAAQEAAAFBAAAAPg4qKm5utrQBBkIEBC/QCRV1zdoOKjYqWlpuWm6CmoIOAho2NjZGRkZabm5uboKCgoKamra22wLbAwMDNwM3gfEAAAJBAAACgQAAAAAAAAAQGGAcFAAACAAAMHCkN/PcPKhkOAf4+Kff2JUH8A/oEQgf4EA4m/SEAAAAAAAAAAA0WJxcM/yRAG/r5CjcrEQEBCAEBBvVKNff0N0z0CP0DXRv8Gic7A/gCAE0LCfgWLPoHKAkaAwn5FGX5BAP4KhoA8SFEAhf+Ny7+DwP/FRAp+hs9JwX1KlgEAf48QQb8//tJOAH3E14d9wAMYwYECO1mLvMDAg0DAgnrVEju9S5o6ggSJjAXAPBGU+sLBfV1Fvj6F3X0AwP4XxwE9g9NPPH/BHwC/AMmVBjnAg0qDR8V/Dgu//8jT/MT+UFY9/IUBFEx4xQASwPvBfcsXPgB/RZFH/pfKfQFJ0MQ/AEA+ng33PMsegToUQULAwcCAAkKWNBAAAAAQQAAUEEAAC4CWlddW1JiAEGQhAELQ214dgxxc3V3YztXbz9vcFB+fH18gXl+F4R/f39+f3qFgoZldneRflZ8eHt3qq1rbfxBAAAQQgAAIEIAAAgQIAgKDBAAQeCEAQuzAX0zGhIPDAsKCQgHBgUEAwIBAMZpLRYPDAsKCQgHBgUEAwIBANWidFM7KyAYEg8MCQcGBQMCAO+7dDscEAsKCQgHBgUEAwIBAPrlvIdWMx4TDQoIBgUEAwIBAPnr1bmcgGdTQjUqIRoVEQ0KAP75686kdk0uGxAKBwUEAwIBAP/9+e/cv5x3VTklFw8KBgQCAP/9+/bt38uzmHxiSzcoHRUPAP/+/ffcompDKhwSDAkGBAMCAEGghgELogEfOWugzc3///////////////9FL0Nvps3///////////////9SSk9fbYCRoK3Nzc3g///g/+B9SjtFYY22//////////////+tc1VJTFxzka3N4OD///////+mhnFmZWZrdn2KkZumtsDAzZbgtoZlU09VYXiRrc3g////////4MCWeGVcWV1mdoagtsDg4OD/4OC2m4Z2bWhmam92g5GgrYMAQdCHAQsR8b6yhFdKKQ4A38GdjGo5JxIAQfCHAQsSg0qNT1CKX2iGX2NbfV1Me3N7AEGQiAELlwGAANYqAOuAFQD0uEgLAPjWgCoHAPjhqlAZBQD77MZ+NhIDAPru059SIw8FAPrny6iAWDUZBgD87ti5lGxHKBIEAP3z4cemgFo5Hw0DAP726dS3k21JLBcKAgD/+vDfxqaAWjohEAYBAP/79OfStZJuSy4ZDAUBAP/9+O7dxKSAXDwjEggDAQD//fny5dC0km5MMBsOBwMBAEGwiQELlwGBAM8yAOyBFAD1uUgKAPnVgSoGAPriqVcbBAD76cKCPhQEAPrsz6BjLxEDAP/w2baDUSkLAQD//unJn2s9FAIBAP/56c6qgFYyFwcBAP/67tm6lGxGJxIGAQD//PPiyKaAWjgeDQQBAP/89efRtJJuTC8ZCwQBAP/9+O3bwqOAXT4lEwgDAQD//vrx4s2xkW9PMx4PBgIBAEHQigELlwGBAMs2AOqBFwD1uEkKAPrXgSkFAPzorVYYAwD98MiBOA8CAP302aReJgoBAP314r2ERxsHAQD99ufLn2k4FwYBAP/469WzhVUvEwUBAP/+893Cn3VGJQwCAQD//vjq0KuAVTAWCAIBAP/++vDcvZVrQyQQBgIBAP/++/PjyaaAWjcdDQUCAQD//vz26tW3k21JKxYKBAIBAEHwiwELlwGCAMg6AOeCGgD0uEwMAPnWgisGAPzorVcYAwD98cuDOA4CAP723adeIwgBAP756MGCQRcFAQD/++/TomMtDwQBAP/789+6g0ohCwMBAP/89ebKnmk5GAgCAQD//ffr1rOEVCwTBwIBAP/++vDfxJ9wRSQPBgIBAP/+/fXn0bCIXTcbCwMCAQD//v38793CnnVMKhIEAwIBAEGSjQELDwIFCQ4UGyMsNkFNWmh3hwBBsI0BCyr+MUNNUl1jxgsSGB8kLf8uQk5XXmjQDhUgKjNC/15obXBzdvg1RVBYX2YAQeiNAQs+AwAAAAIAAAADAAAAAgAAAAUAAAACAAAAAwAAAAIAAAADAAAAAgAAAAUAAAACAAAAAwAAAAIAAAAAAQAAAAEAQbKOAQv5AQH/Af8C/gL+A/0AAQAB/wL/Av4D/gP9B/4HAAAAAAAC////AAABAQABAAEAAAAAAAEAAAAAAAEAAAABAAAAAAD/AgEAAQEAAP//AAAAAAAAAf8AAf8A/wH+Av7+Av0CA/38A/wEBPsF+vsG+QYFCPcAAAEAAAAAAAAA/wEAAAH/AAH//wH/AgH/Av7+Av4CAgP9AAEAAAAAAAABAAEAAAH/AQAAAgH/Av//Av8CAv8D/v7+AwABAAABAAH/Av8C/wID/gP+/gQE/QX9/Ab8BgX7CPr7+QkAAAAAAAAAAPsI/wb/BvwK+gr+Bv8G+wr3DP0H/gf5DRAYIgBBtpABC4wi4D8AAAAAAADgv109f2aeoOY/AAAAAACIOT1EF3X6UrDmPwAAAAAAANg8/tkLdRLA5j8AAAAAAHgovb921N3cz+Y/AAAAAADAHj0pGmU8st/mPwAAAAAAANi84zpZmJLv5j8AAAAAAAC8vIaTUfl9/+Y/AAAAAADYL72jLfRmdA/nPwAAAAAAiCy9w1/s6HUf5z8AAAAAAMATPQXP6oaCL+c/AAAAAAAwOL1SgaVImj/nPwAAAAAAwAC9/MzXNb1P5z8AAAAAAIgvPfFnQlbrX+c/AAAAAADgAz1IbauxJHDnPwAAAAAA0Ce9OF3eT2mA5z8AAAAAAADdvAAdrDi5kOc/AAAAAAAA4zx4AetzFKHnPwAAAAAAAO28YNB2CXux5z8AAAAAAEAgPTPBMAHtwec/AAAAAAAAoDw2hv9iatLnPwAAAAAAkCa9O07PNvPi5z8AAAAAAOACvejDkYSH8+c/AAAAAABYJL1OGz5UJwToPwAAAAAAADM9GgfRrdIU6D8AAAAAAAAPPX7NTJmJJeg/AAAAAADAIb3QQrkeTDboPwAAAAAA0Ck9tcojRhpH6D8AAAAAABBHPbxbnxf0V+g/AAAAAABgIj2vkUSb2WjoPwAAAAAAxDK9laMx2cp56D8AAAAAAAAjvbhlitnHiug/AAAAAACAKr0AWHik0JvoPwAAAAAAAO28I6IqQuWs6D8AAAAAACgzPfoZ1roFvug/AAAAAAC0Qj2DQ7UWMs/oPwAAAAAA0C69TGYIXmrg6D8AAAAAAFAgvQd4FZmu8eg/AAAAAAAoKD0OLCjQ/gLpPwAAAAAAsBy9lv+RC1sU6T8AAAAAAOAFvfkvqlPDJek/AAAAAABA9TxKxs2wNzfpPwAAAAAAIBc9rphfK7hI6T8AAAAAAAAJvctSyMtEWuk/AAAAAABoJT0hb3aa3WvpPwAAAAAA0Da9Kk7en4J96T8AAAAAAAABvaMjeuQzj+k/AAAAAAAALT0EBspw8aDpPwAAAAAApDi9if9TTbuy6T8AAAAAAFw1PVvxo4KRxOk/AAAAAAC4Jj3FuEsZdNbpPwAAAAAAAOy8jiPjGWPo6T8AAAAAANAXPQLzB41e+uk/AAAAAABAFj1N5V17ZgzqPwAAAAAAAPW89riO7Xoe6j8AAAAAAOAJPScuSuybMOo/AAAAAADYKj1dCkaAyULqPwAAAAAA8Bq9myU+sgNV6j8AAAAAAGALPRNi9IpKZ+o/AAAAAACIOD2nszATnnnqPwAAAAAAIBE9jS7BU/6L6j8AAAAAAMAGPdL8eVVrnuo/AAAAAAC4Kb24bzUh5bDqPwAAAAAAcCs9gfPTv2vD6j8AAAAAAADZPIAnPDr/1eo/AAAAAAAA5Dyj0lqZn+jqPwAAAAAAkCy9Z/Mi5kz76j8AAAAAAFAWPZC3jSkHDus/AAAAAADULz2piZpsziDrPwAAAAAAcBI9SxpPuKIz6z8AAAAAAEdNPedHtxWERus/AAAAAAA4OL06WeWNclnrPwAAAAAAAJg8asXxKW5s6z8AAAAAANAKPVBe+/J2f+s/AAAAAACA3jyySSfyjJLrPwAAAAAAwAS9AwahMLCl6z8AAAAAAHANvWZvmrfguOs/AAAAAACQDT3/wUuQHszrPwAAAAAAoAI9b6Hzw2nf6z8AAAAAAHgfvbgd11vC8us/AAAAAACgEL3pskFhKAbsPwAAAAAAQBG94FKF3ZsZ7D8AAAAAAOALPe5k+tkcLew/AAAAAABACb0v0P9fq0DsPwAAAAAA0A69Ff36eEdU7D8AAAAAAGY5PcvQVy7xZ+w/AAAAAAAQGr22wYiJqHvsPwAAAACARVi9M+cGlG2P7D8AAAAAAEgavd/EUVdAo+w/AAAAAAAAyzyUkO/cILfsPwAAAAAAQAE9iRZtLg/L7D8AAAAAACDwPBLEXVUL3+w/AAAAAABg8zw7q1tbFfPsPwAAAAAAkAa9vIkHSi0H7T8AAAAAAKAJPfrICCtTG+0/AAAAAADgFb2Fig0Ihy/tPwAAAAAAKB09A6LK6shD7T8AAAAAAKABPZGk+9wYWO0/AAAAAAAA3zyh5mLodmztPwAAAAAAoAO9ToPJFuOA7T8AAAAAANgMvZBg/3Fdle0/AAAAAADA9DyuMtsD5qntPwAAAAAAkP88JYM61ny+7T8AAAAAAIDpPEW0AfMh0+0/AAAAAAAg9by/BRxk1eftPwAAAAAAcB297Jp7M5f87T8AAAAAABQWvV59GWtnEe4/AAAAAABICz3no/UURibuPwAAAAAAzkA9XO4WOzM77j8AAAAAAGgMPbQ/i+cuUO4/AAAAAAAwCb1obWckOWXuPwAAAAAAAOW8REzH+1F67j8AAAAAAPgHvSa3zXd5j+4/AAAAAABw87zokKSir6TuPwAAAAAA0OU85Mp8hvS57j8AAAAAABoWPQ1oji1Iz+4/AAAAAABQ9TwUhRiiquTuPwAAAAAAQMY8E1ph7hv67j8AAAAAAIDuvAZBthycD+8/AAAAAACI+rxjuWs3KyXvPwAAAAAAkCy9dXLdSMk67z8AAAAAAACqPCRFblt2UO8/AAAAAADw9Lz9RIh5MmbvPwAAAAAAgMo8OL6crf177z8AAAAAALz6PII8JALYke8/AAAAAABg1LyOkJ6BwafvPwAAAAAADAu9EdWSNrq97z8AAAAAAODAvJRxjyvC0+8/AAAAAIDeEL3uIypr2envPwAAAAAAQ+48AAAAAAAA8D8AAAAAAAAAAL68WvoaC/A/AAAAAABAs7wDM/upPRbwPwAAAAAAFxK9ggI7FGgh8D8AAAAAAEC6PGyAdz6aLPA/AAAAAACY7zzKuxEu1DfwPwAAAAAAQMe8iX9u6BVD8D8AAAAAADDYPGdU9nJfTvA/AAAAAAA/Gr1ahRXTsFnwPwAAAAAAhAK9lR88Dgpl8D8AAAAAAGDxPBr33SlrcPA/AAAAAAAkFT0tqHIr1HvwPwAAAAAAoOm80Jt1GEWH8D8AAAAAAEDmPMgHZva9kvA/AAAAAAB4AL2D88bKPp7wPwAAAAAAAJi8MDkfm8ep8D8AAAAAAKD/PPyI+WxYtfA/AAAAAADI+ryKbORF8cDwPwAAAAAAwNk8FkhyK5LM8D8AAAAAACAFPdhdOSM72PA/AAAAAADQ+rzz0dMy7OPwPwAAAAAArBs9pqnfX6Xv8D8AAAAAAOgEvfDS/q9m+/A/AAAAAAAwDb1LI9coMAfxPwAAAAAAUPE8W1sS0AET8T8AAAAAAADsPPkqXqvbHvE/AAAAAAC8Fj3VMWzAvSrxPwAAAAAAQOg8fQTyFKg28T8AAAAAANAOvektqa6aQvE/AAAAAADg6Dw4MU+TlU7xPwAAAAAAQOs8cY6lyJha8T8AAAAAADAFPd/DcVSkZvE/AAAAAAA4Az0RUn08uHLxPwAAAAAA1Cg9n7uVhtR+8T8AAAAAANAFvZONjDj5ivE/AAAAAACIHL1mXTdYJpfxPwAAAAAA8BE9p8tv61uj8T8AAAAAAEgQPeOHE/iZr/E/AAAAAAA5R71UXQSE4LvxPwAAAAAA5CQ9QxwolS/I8T8AAAAAACAKvbK5aDGH1PE/AAAAAACA4zwxQLRe5+DxPwAAAAAAwOo8ONn8IlDt8T8AAAAAAJABPffNOITB+fE/AAAAAAB4G72PjWKIOwbyPwAAAAAAlC09Hqh4Nb4S8j8AAAAAAADYPEHdfZFJH/I/AAAAAAA0Kz0jE3mi3SvyPwAAAAAA+Bk952F1bno48j8AAAAAAMgZvScUgvsfRfI/AAAAAAAwAj0CprJPzlHyPwAAAAAASBO9sM4ecYVe8j8AAAAAAHASPRZ94mVFa/I/AAAAAADQET0P4B00DnjyPwAAAAAA7jE9PmP14d+E8j8AAAAAAMAUvTC7kXW6kfI/AAAAAADYE70J3x/1nZ7yPwAAAAAAsAg9mw7RZoqr8j8AAAAAAHwivTra2tB/uPI/AAAAAAA0Kj35Gnc5fsXyPwAAAAAAgBC92QLkpoXS8j8AAAAAANAOvXkVZB+W3/I/AAAAAAAg9LzPLj6pr+zyPwAAAAAAmCS9Ioi9StL58j8AAAAAADAWvSW2MQr+BvM/AAAAAAA2Mr0Lpe7tMhTzPwAAAACA33C9uNdM/HAh8z8AAAAAAEgivaLpqDu4LvM/AAAAAACYJb1mF2SyCDzzPwAAAAAA0B49J/rjZmJJ8z8AAAAAAADcvA+fkl/FVvM/AAAAAADYML25iN6iMWTzPwAAAAAAyCI9Oao6N6dx8z8AAAAAAGAgPf50HiMmf/M/AAAAAABgFr042AVtrozzPwAAAAAA4Aq9wz5xG0Ca8z8AAAAAAHJEvSCg5TTbp/M/AAAAAAAgCD2Vbuy/f7XzPwAAAAAAgD498qgTwy3D8z8AAAAAAIDvPCLh7UTl0PM/AAAAAACgF727NBJMpt7zPwAAAAAAMCY9zE4c33Ds8z8AAAAAAKZIvYx+rARF+vM/AAAAAADcPL27oGfDIgj0PwAAAAAAuCU9lS73IQoW9D8AAAAAAMAePUZGCSf7I/Q/AAAAAABgE70gqVDZ9TH0PwAAAAAAmCM967mEP/o/9D8AAAAAAAD6PBmJYWAITvQ/AAAAAADA9rwB0qdCIFz0PwAAAAAAwAu9FgAd7UFq9D8AAAAAAIASvSYzi2ZtePQ/AAAAAADgMD0APMG1oob0PwAAAAAAQC29BK+S4eGU9D8AAAAAACAMPXLT1/Aqo/Q/AAAAAABQHr0BuG3qfbH0PwAAAAAAgAc94Sk21dq/9D8AAAAAAIATvTLBF7hBzvQ/AAAAAACAAD3b3f2Zstz0PwAAAAAAcCw9lqvYgS3r9D8AAAAAAOAcvQItnXay+fQ/AAAAAAAgGT3BMUV/QQj1PwAAAAAAwAi9KmbPotoW9T8AAAAAAAD6vOpRP+h9JfU/AAAAAAAISj3aTp1WKzT1PwAAAAAA2Ca9Gqz29OJC9T8AAAAAAEQyvduUXcqkUfU/AAAAAAA8SD1rEendcGD1PwAAAAAAsCQ93im1Nkdv9T8AAAAAAFpBPQ7E4tsnfvU/AAAAAADgKb1vx5fUEo31PwAAAAAACCO9TAv/Jwic9T8AAAAAAOxNPSdUSN0Hq/U/AAAAAAAAxLz0eqj7Ebr1PwAAAAAACDA9C0ZZiibJ9T8AAAAAAMgmvT+OmZBF2PU/AAAAAACaRj3hIK0Vb+f1PwAAAAAAQBu9yuvcIKP29T8AAAAAAHAXPbjcdrnhBfY/AAAAAAD4Jj0V983mKhX2PwAAAAAAAAE9MVU6sH4k9j8AAAAAANAVvbUpGR3dM/Y/AAAAAADQEr0Tw8w0RkP2PwAAAAAAgOq8+o68/rlS9j8AAAAAAGAovZczVYI4YvY/AAAAAAD+cT2OMgjHwXH2PwAAAAAAIDe9fqlM1FWB9j8AAAAAAIDmPHGUnrH0kPY/AAAAAAB4Kb0AIP4f9h/qH9gfwh+oH4gfYh86Hwof2B6gHmIeIh7cHZAdQh3uHJYcOhzYG3IbChucGioatBk6GbwYPBi2Fy4XoBYQFn4V6BROFLATEBNuEsgRHhF0EMYPFg9kDq4N+AxADIQLyAoKCkoJigjGBwIHPgZ4BbIE6gMiA1oCkgHKAAAANv9u/qb93vwW/E77iPrC+f74Ovh297b29vU49Xz0wPMI81LynPHq8DrwjO/i7jjuku3w7FDssusY64Lq8Olg6dLoSujE50TnxuZM5tblZOX25I7kKOTG42rjEuO+4nDiJOLe4Z7hYOEo4fbgxuCe4HjgWOA+4CjgFuAK4ALgAOAAQdGyAQuKBQ8IBwQLDAMCDQoFBgkOAQAJBgMEBQgBAgcAAAAAAAAADyc0PURKT1RYXF9jZmlsb3J1d3p8foGDhYeJi46PkZOVl5mbnZ6goqOlp6iqq62usLGztLa3ubq7vb7AwcLExcfIycvMzc/Q0dPU1dfY2dvc3d/g4ePk5ufo6uvs7u/x8vP19vj5+vz9/wAAAAAAAAAcKzQ7QUZKTlFVV1pdX2JkZmlrbW9xc3R2eHp7fX+AgoOFhoiJioyNj5CRk5SVl5iZmpydnp+goqOkpaanqKmrrK2ur7CxsrO0tba3uLm6u7y8vb6/wMHCw8TFxsfIycrLy8zNzs/Q0dLT1NXW1tfY2drb3N3e3+Dg4eLj5OXm5+jp6uvs7O3u7/Dx8vP09fb3+Pn6+/z9/v8AAAAAAAAAAAgdKTE4PkJGSk1QU1ZYW11fYWNlZ2lrbG5wcXN0dnd5ent9fn+BgoOEhoeIiYqMjY6PkJGSk5SVlpeYmZqcnZ6fn6ChoqOkpaanqKmqq6usra6vsLGxsrO0tbW2t7i5ubq7vL29vr/AwMHCw8PExcbGx8jIycrLy8zNzs7P0NHR0tPT1NXW1tfY2Nna29vc3d3e3+Dg4eLi4+Tl5ebn6Ojp6urr7O3t7u/w8PHy8/P09fb29/j5+fr7/P3/AIC7AAB4AAAAFQAAABUAAAAAmlk/AAAAAAAAgD8AAIA/sFsAAAMAAAAIAAAAeAAAAAsAAADgWwAA0FwAAABdAACABwAAAwAAAOBeAAAAkwAAMJQAAOiUAAAgXwAAiAEAAEB7AAAgfAAAsH0AAAAAAAAAAAEAAgADAAQABQAGAAcACAAKAAwADgAQABQAGAAcACIAKAAwADwATgBkAEH1twEL0gFaUEtFPzgxKCIdFBIKAAAAAAAAAABuZFpUTkdBOjMtJyAaFAwAAAAAAAB2bmddVlBLRkE7NS8oHxcPBAAAAAB+d3BoX1lTTkhCPDYvJyAZEQwBAACGf3hyZ2FbVU5IQjw2LykjHRcQCgGQiYJ8cWtlX1hSTEZAOTMtJyEaDwGYkYqEe3VvaWJcVlBKQz03MSskFAGim5SOhX95c2xmYFpUTUdBOzUuHgGspZ6Yj4mDfXZwamReV1FLRT84LRTIyMjIyMjIyMbBvLeyraijnpmUgWgAQeC5AQufBAgACAAIAAgAEAAQABAAFQAVABgAHQAiACQAAAAAAAAAahyNOFK7HjoIadw6gu1XO4ljsjsDKgU8MNw5PLQ+dzwco5480fLFPP6G8TybqxA9Ba0qPYTCRj1T5mQ9EYmCPYefkz3LsqU90b64PTq/zD1Ur+E9FIr3PQ4lBz7Z9BI+XzEfPmjXKz6K4zg+MFJGPpQfVD6/R2I+jsZwPrCXfz5SW4c+YA+PPpjllj55254+cO6mPtgbrz77YLc+Ebu/PkYnyD63otA+eCrZPpS74T4MU+o+3u3yPgaJ+z6+EAI/H1oGPySfCj9Q3g4/KxYTP0FFFz8lahs/c4MfP86PIz/mjSc/dHwrPz9aLz8ZJjM/5942P5mDOj8zEz4/xYxBP3fvRD9/Okg/J21LP86GTj/lhlE/8WxUP444Vz9p6Vk/RX9cP/r5Xj9zWWE/r51jP8HGZT/P1Gc/EchpP9Kgaz9uX20/UARvP/SPcD/mAnI/vV1zPx+hdD+/zXU/V+R2P7Dldz+X0ng/46t5P3Nyej8nJ3s/58p7P51efD8143w/nFl9P73CfT+GH34/3nB+P6u3fj/P9H4/Jil/P4ZVfz++en8/lpl/P8yyfz8Ux38/HNd/P4Ljfz/d7H8/tvN/P4r4fz/I+38/1v1/Pwf/fz+l/38/6P9/P/3/fz8AAIA/4AEAAIeICDv/////BQBgAAMAIAAEAAgAAgAEAAQAAQBBjL4BCwZgfgAAIIIAQaC+AQvJOP//fz+O/38/av5/P5P8fz8H+n8/yPZ/P9byfz8w7n8/1uh/P8jifz8H3H8/k9R/P2vMfz+Pw38/ALp/P72vfz/HpH8/HZl/P8CMfz+wf38/7HF/P3Zjfz9LVH8/bkR/P94zfz+aIn8/oxB/P/r9fj+d6n4/jdZ+P8vBfj9WrH4/LpZ+P1N/fj/GZ34/hk9+P5Q2fj/vHH4/mAJ+P4/nfT/Ty30/Zq99P0aSfT90dH0/8VV9P7w2fT/VFn0/PPZ8P/LUfD/2snw/SZB8P+tsfD/bSHw/GyR8P6n+ez+H2Hs/tLF7PzCKez/8YXs/Fzl7P4IPez895Xo/SLp6P6KOej9NYno/SDV6P5QHej8w2Xk/Hap5P1p6eT/pSXk/yBh5P/nmeD97tHg/ToF4P3NNeD/qGHg/suN3P82tdz86d3c/+T93PwoIdz9uz3Y/JZZ2Py9cdj+MIXY/POZ1P0CqdT+XbXU/QjB1P0HydD+Us3Q/O3R0Pzc0dD+H83M/LLJzPyZwcz92LXM/GupyPxSmcj9kYXI/ChxyPwXWcT9Xj3E/AEhxP///cD9Vt3A/Am5wPwYkcD9i2W8/FY5vPyBCbz+E9W4/P6huP1Nabj/AC24/hrxtP6VsbT8dHG0/78psPxt5bD+hJmw/gNNrP7t/az9QK2s/QNZqP4yAaj8yKmo/NdNpP5N7aT9NI2k/ZMpoP9hwaD+oFmg/1btnP2BgZz9IBGc/j6dmPzNKZj827GU/l41lP1cuZT93zmQ/9W1kP9QMZD8Sq2M/sUhjP7DlYj8QgmI/0R1iP/O4YT93U2E/XO1gP6SGYD9OH2A/W7dfP8tOXz+e5V4/1XteP3ARXj9upl0/0jpdP5rOXD/GYVw/WfRbP1GGWz+uF1s/cqhaP504Wj8uyFk/J1dZP4flWD9Pc1g/fwBYPxeNVz8YGVc/gqRWP1YvVj+TuVU/OkNVP0vMVD/HVFQ/rtxTPwFkUz+/6lI/6XBSP3/2UT+Ce1E/8v9QP8+DUD8aB1A/0olPP/oLTz+QjU4/lA5OPwmPTT/tDk0/QY5MPwUNTD87i0s/4QhLP/mFSj+DAko/f35JP+75SD/PdEg/JO9HP+1oRz8p4kY/2lpGPwDTRT+bSkU/rMFEPzI4RD8vrkM/oiNDP42YQj/vDEI/yIBBPxr0QD/lZkA/KNk/P+VKPz8bvD4/zCw+P/ecPT+dDD0/vns8P1zqOz91WDs/CsY6Px0zOj+tnzk/uws5P0d3OD9R4jc/2kw3P+O2Nj9rIDY/dIk1P/3xND8HWjQ/k8EzP6AoMz8wjzI/QvUxP9haMT/xvzA/jiQwP6+ILz9V7C4/gU8uPzKyLT9pFC0/J3YsP2vXKz83OCs/i5gqP2f4KT/MVyk/urYoPzIVKD8zcyc/v9AmP9YtJj95iiU/p+YkP2FCJD+pnSM/ffgiP99SIj/PrCE/TQYhP1tfID/4tx8/JRAfP+JnHj8wvx0/EBYdP4FsHD+Ewhs/GhgbP0NtGj8Awhk/URYZPzZqGD+xvRc/wRAXP2djFj+jtRU/dgcVP+FYFD/kqRM/f/oSP7NKEj+AmhE/5+kQP+g4ED+Ehw8/u9UOP44jDj/+cA0/Cr4MP7MKDD/6Vgs/36IKP2PuCT+GOQk/SYQIP6zOBz+vGAc/VGIGP5urBT+D9AQ/Dz0EPz2FAz8PzQI/hhQCP6FbAT9hogA/j9H/Pqdd/j4O6fw+wnP7Psb9+T4bh/g+wQ/3PrqX9T4GH/Q+qKXyPp4r8T7ssO8+kTXuPpC57D7oPOs+mr/pPqlB6D4Vw+Y+30PlPgjE4z6RQ+I+fMLgPshA3z54vt0+jDvcPga42j7mM9k+Lq/XPt8p1j75o9Q+fR3TPm6W0T7MDtA+l4bOPtL9zD59dMs+merJPidgyD4o1cY+n0nFPoq9wz7sMMI+xqPAPhkWvz7mh70+Lfm7PvFpuj4y2rg+8Um3Pi+5tT7uJ7Q+L5ayPvIDsT45ca8+BN6tPlZKrD4vtqo+kCGpPnqMpz7v9qU+72CkPnzKoj6XM6E+QJyfPnoEnj5EbJw+odOaPpE6mT4WoZc+MAeWPuFslD4p0pI+CzeRPoebjz6e/40+UWOMPqLGij6RKYk+IIyHPlDuhT4iUIQ+l7GCPrASgT7e5n4+qad7PsNneD4vJ3U+7uVxPgSkbj5zYWs+PB5oPmLaZD7olWE+z1BePhoLWz7MxFc+5n1UPms2UT5d7k0+v6VKPpJcRz7aEkQ+l8hAPs59PT6AMjo+ruY2Pl2aMz6NTTA+QgAtPn2yKT5CZCY+kRUjPm7GHz7bdhw+2iYZPm3WFT6YhRI+WzQPPrriCz63kAg+VD4FPpTrAT7wMP09Bor2PXHi7z0zOuk9T5HiPc/n2z21PdU9A5POPcDnxz3yO8E9nI+6PcPisz1sNa09m4emPVXZnz2fKpk9fnuSPfbLiz0LHIU9h9d8PUZ2bz1dFGI91rFUPblORz0Q6zk95YYsPUAiHz0svRE9slcEPbXj7TxgF9M8dkq4PAt9nTwyr4I8+sFPPP4kGjwqD8k7mac7Oy591rnSRnG7q97ju6aMJ7yBKV284WKJvKAwpLzs/b68s8rZvOCW9LwxsQe9kxYVvYx7Ir0T4C+9HkQ9vaWnSr2dCli9/mxlvb7Ocr3qF4C9G8iGve13jb1cJ5S9Y9aavf2Eob0mM6i92eCuvRGOtb3KOry9/ubCvaqSyb3IPdC9VOjWvUqS3b2kO+S9XeTqvXKM8b3dM/i9mtr+vVLAAr78Ega+R2UJvjK3DL66CBC+3VkTvpiqFr7q+hm+0EodvkeaIL5O6SO+4TcnvgCGKr6m0y2+0yAxvoNtNL61uTe+ZQU7vpNQPr46m0G+WuVEvvAuSL75d0u+dMBOvl0IUr6zT1W+c5ZYvpzcW74qIl++G2divm2rZb4f72i+LDJsvpR0b75UtnK+avd1vtM3eb6Nd3y+lrZ/vnV6gb5FGYO+ubeEvtBVhr6I84e+4ZCJvtoti75wyoy+pGaOvnQCkL7fnZG+5DiTvoHTlL62bZa+gQeYvuKgmb7XOZu+X9Kcvnlqnr4jAqC+XpmhviYwo759xqS+YFymvs7xp77Ghqm+RxurvlCvrL7gQq6+9dWvvo9osb6t+rK+TYy0vm4dtr4Qrre+MD65vs/Nur7qXLy+guu9vpR5v74fB8G+I5TCvp8gxL6RrMW++DfHvtPCyL4iTcq+4tbLvhNgzb616M6+xXDQvkL40b4tf9O+gwXVvkOL1r5tENi+/5TZvvkY275ZnNy+HR/evkah377TIuG+waPivhAk5L6+o+W+zCLnvjih6L4AH+q+JJzrvqIY7b56lO6+qw/wvjOK8b4SBPO+Rn30vs/19b6qbfe+2eT4vlhb+r4o0fu+R0b9vrW6/r44FwC/u9AAv+SJAb+yQgK/JfsCvzuzA7/2agS/UyIFv1PZBb/1jwa/OEYHvx38B7+isQi/x2YJv4wbCr/wzwq/84MLv5M3DL/R6gy/rJ0NvyRQDr84Ag+/6LMPvzJlEL8YFhG/l8YRv7B2Er9jJhO/rtUTv5GEFL8NMxW/H+EVv8iOFr8IPBe/3egXv0iVGL9IQRm/3OwZvwSYGr/AQhu/D+0bv/CWHL9jQB2/aOkdv/6RHr8lOh+/3OEfvyOJIL/6LyG/X9Yhv1J8Ir/UISO/48Yjv39rJL+nDyW/XLMlv51WJr9o+Sa/v5snv6A9KL8L3yi//38pv30gKr+DwCq/EWArvyf/K7/EnSy/6Dstv5LZLb/Ddi6/eRMvv7SvL79zSzC/t+Ywv3+BMb/LGzK/mbUyv+pOM7+95zO/EoA0v+gXNb8/rzW/FkY2v27cNr9Fcje/nAc4v3GcOL/FMDm/lsQ5v+ZXOr+y6jq//Hw7v8IOPL8DoDy/wTA9v/rAPb+tUD6/298+v4NuP7+l/D+/QIpAv1MXQb/go0G/5C9Cv2C7Qr9TRkO/vtBDv55aRL/240S/wmxFvwX1Rb+8fEa/6ANHv4mKR7+dEEi/JZZIvyAbSb+On0m/byNKv8GmSr+GKUu/vKtLv2MtTL96rky/Ai9Nv/quTb9iLk6/Oa1Ov34rT78zqU+/VSZQv+aiUL/kHlG/UJpRvygVUr9tj1K/HglTvzuCU7/D+lO/t3JUvxbqVL/fYFW/EtdVv7BMVr+3wVa/JzZXvwCqV79CHVi/7I9Yv/4BWb94c1m/WeRZv6JUWr9RxFq/ZjNbv+KhW7/DD1y/Cn1cv7fpXL/IVV2/PsFdvxgsXr9Xll6/+f9ev/9oX79o0V+/Mzlgv2KgYL/zBmG/5WxhvzrSYb/wNmK/CJtiv4D+Yr9ZYWO/ksNjvywlZL8lhmS/fuZkvzdGZb9OpWW/xQNmv5phZr/Nvma/Xhtnv013Z7+a0me/RC1ov0uHaL+u4Gi/bzlpv4uRab8E6Wm/2T9qvwmWar+U62q/e0Brv7yUa79Z6Gu/Tztsv6CNbL9L32y/TzBtv62Abb9l0G2/dR9uv99tbr+hu26/uwhvvy5Vb7/4oG+/G+xvv5U2cL9ngHC/kMlwvw8Scb/mWXG/E6Fxv5fncb9xLXK/oHJyvya3cr8B+3K/Mj5zv7iAc7+UwnO/xAN0v0lEdL8ihHS/UMN0v9IBdb+oP3W/0nx1v1C5db8h9XW/RTB2v71qdr+IpHa/pt12vxYWd7/ZTXe/74R3v1e7d78R8Xe/HSZ4v3paeL8qjni/K8F4v33zeL8hJXm/FlZ5v1yGeb/ytXm/2uR5vxITer+aQHq/c216v52Zer8WxXq/3+96v/gZe79hQ3u/Gmx7vyKUe796u3u/IOJ7vxcIfL9cLXy/8FF8v9N1fL8FmXy/hrt8v1XdfL9z/ny/3x59v5o+fb+jXX2/+nt9v5+Zfb+Stn2/09J9v2Lufb8/CX6/aSN+v+E8fr+nVX6/um1+vxuFfr/Jm36/xLF+vw3Hfr+i236/he9+v7UCf78yFX+//CZ/vxM4f792SH+/J1h/vyRnf79udX+/BYN/v+iPf78ZnH+/lad/v1+yf790vH+/18V/v4XOf7+B1n+/yN1/v13kf7896n+/au9/v+Pzf7+p93+/u/p/vxn9f7/E/n+/u/9/v/r/fz85/n8/qfl/P0vyfz8e6H8/I9t/P1nLfz/BuH8/W6N/PyiLfz8ncH8/WlJ/P78xfz9YDn8/Jeh+Pya/fj9ck34/yGR+P2kzfj9B/30/T8h9P5aOfT8UUn0/yxJ9P7zQfD/ni3w/TUR8P+/5ez/NrHs/6Vx7P0MKez/dtHo/tlx6P9EBej8upHk/zkN5P7LgeD/ceng/TBJ4PwSndz8EOXc/T8h2P+RUdj/G3nU/9mV1P3XqdD9EbHQ/ZetzP9pncz+j4XI/wlhyPznNcT8JP3E/NK5wP7sacD+ghG8/5OtuP4pQbj+Tsm0/ARJtP9VubD8RyWs/tyBrP8l1aj9JyGk/ORhpP5tlaD9vsGc/uvhmP3w+Zj+4gWU/b8JkP6QAZD9aPGM/kXViP0ysYT+O4GA/WRJgP65BXz+Rbl4/A5ldPwjBXD+g5ls/zwlbP5gqWj/7SFk//WRYP59+Vz/llVY/0KpVP2O9VD+hzVM/jNtSPyfnUT918FA/efdPPzT8Tj+r/k0/3/5MP9T8Sz+M+Eo/CvJJP1LpSD9l3kc/R9FGP/vBRT+EsEQ/5ZxDPyCHQj86b0E/NFVAPxM5Pz/YGj4/iPo8PybYOz+0szo/No05P69kOD8iOjc/kw02PwXfND98rjM/+XsyP4JHMT8ZETA/wtguP3+eLT9WYiw/SCQrP1rkKT+Qoig/614nP3EZJj8l0iQ/CYkjPyM+Ij918SA/BKMfP9JSHj/kAB0/Pa0bP+FXGj/TABk/GagXP7RNFj+q8RQ//ZMTP7I0Ej/M0xA/UHEPP0INDj+kpww/fEALP83XCT+abQg/6QEHP72UBT8ZJgQ/A7YCP35EAT8co/8+brr8PvrO+T7K4PY+5O/zPlH88D4aBu4+Rw3rPuAR6D7tE+U+dxPiPocQ3z4kC9w+WAPZPir51T6k7NI+zd3PPq/MzD5Suck+v6PGPv6Lwz4YcsA+Fla9PgA4uj7gF7c+vfWzPqHRsD6Vq60+ooOqPs9Zpz4nLqQ+sgChPnnRnT6FoJo+322XPo85lD6gA5E+GsyNPgWTij5rWIc+VhyEPs3egD62P3s+EL90Prs7bj7JtWc+TS1hPlmiWj7/FFQ+UYVNPmPzRj5GX0A+Dck5PsowMz6Qliw+cvolPoJcHz7SvBg+dhsSPn94Cz4B1AQ+HVz8PXIN7z0pvOE9ZmjUPU4Sxz0Iurk9uF+sPYQDnz2SpZE9B0aEPRLKbT16BVM9kT44PaR1HT38qgI9yr3PPFYjmjxhDkk8xae7Oz16VroJRvG7Et1jvFCKp7xBJN28410JvSMoJL2W8D698rZZvep6dL0anoe9Qv2Uvchaor2Gtq+9VxC9vRZoyr2bvde9wxDlvWlh8r1lr/+9Sn0GvmghDb76wxO+7WQavi4EIb6soSe+Uz0uvhDXNL7Sbju+hgRCvhmYSL55KU++lLhVvlZFXL6uz2K+iVdpvtbcb76AX3a+eN98vlSugb6B64S+OCeIvnJhi74kmo6+RdGRvs0Glb6zOpi+7mybvnSdnr49zKG+QPmkvnMkqL7PTau+SXWuvtqasb54vrS+G+C3vrr/ur5LHb6+xzjBviVSxL5bace+YX7KvjCRzb68odC+ALDTvvG71r6Hxdm+uszcvoHR377T0+K+qdPlvvrQ6L69y+u+6sPuvni58b5grPS+mpz3vhyK+r7fdP2+bS4AvwOhAb8tEgO/5oEEvyzwBb/6XAe/TMgIvx4yCr9smgu/MgENv2xmDr8Xyg+/LSwRv6yMEr+Q6xO/1UgVv3akFr9x/he/wFYZv2KtGr9RAhy/ilUdvwmnHr/L9h+/zEQhvwmRIr982yO/JCQlv/1qJr8CsCe/MPMov4Q0Kr/6cyu/j7Esvz/tLb8HJy+/414wv9CUMb/KyDK/zvozv9oqNb/oWDa/94Q3vwKvOL8H1zm/A/06v/EgPL/PQj2/mmI+v0+AP7/pm0C/aLVBv8bMQr8B4kO/F/VEvwMGRr/EFEe/ViFIv7YrSb/hM0q/1DlLv409TL8JP02/RD5Ovz07T7/wNVC/Wi5Rv3kkUr9KGFO/yglUv/f4VL/O5VW/TdBWv3C4V783nli/nIFZv6BiWr8+QVu/dR1cv0H3XL+izl2/lKNevxR2X78iRmC/uhNhv9neYb9/p2K/qW1jv1QxZL9+8mS/JrFlv0ltZr/lJme/+N1nv4CSaL97RGm/6PNpv8Ogar8MS2u/wPJrv96XbL9kOm2/UNptv6B3br9TEm+/Zqpvv9k/cL+p0nC/1WJxv1vwcb86e3K/cQNzv/2Ic7/eC3S/EYx0v5YJdb9rhHW/j/x1vwBydr+95Ha/xlR3vxjCd7+yLHi/k5R4v7v5eL8oXHm/2bt5v80Yer8Cc3q/ecp6vy8fe78kcXu/WMB7v8kMfL92Vny/X518v4LhfL/gIn2/d2F9v0edfb9P1n2/jgx+vwRAfr+wcH6/kp5+v6nJfr/18X6/dRd/vyk6f78QWn+/K3d/v3iRf7/4qH+/qr1/v4/Pf7+l3n+/7ep/v2b0f78R+3+/7f5/v+r/fz/l+H8/puZ/Py3Jfz98oH8/lWx/P3ktfz8s434/sY1+Pwstfj8/wX0/Ukp9P0jIfD8oO3w/96J7P73/ej+AUXo/SJh5Px7UeD8JBXg/Eyt3P0ZGdj+sVnU/Tlx0PzhXcz92R3I/Ey1xPxwIcD+e2G4/pZ5tP0BabD9+C2s/a7JpPxlPaD+W4WY/8mllPz7oYz+LXGI/6sZgP20nXz8mfl0/KMtbP4UOWj9TSFg/o3hWP4ufVD8gvVI/dtFQP6PcTj+93kw/29dKPxPISD98r0Y/Lo5EP0FkQj/OMUA/7PY9P7SzOz9CaDk/rRQ3PxC5ND+GVTI/KeovPxV3LT9l/Co/NXooP6HwJT/GXyM/wMcgP6woHj+pghs/1NUYP0oiFj8qaBM/k6cQP6TgDT97Ews/OUAIP/1mBT/nhwI/LUb/Pltx+T6XkfM+JKftPkWy5z48s+E+TKrbPrqX1T7Je88+vlbJPt8owz5w8rw+t7O2PvtssD6BHqo+ksijPnNrnT5sB5c+xZyQPscrij65tIM+x296PiFrbT4RXGA+KUNTPv0gRj4g9jg+JsMrPqSIHj4tRxE+V/8DPm5j7T3CvdI92g64Pd5XnT37mYI9vKxPPWUcGj2ZCsk8Kqc7PMF41rotRHG8V9fjvEyBJ72UD129FUqJvVoGpL1tu769ImjZvU4L9L3jUQe+L5gUvvfXIb6lEC++pkE8vmRqSb5Nila+zaBjvlCtcL5Fr32+DVOFvp7Ii74NOJK+EqGYvmYDn76/XqW+2LKrvmn/sb4rRLi+2IC+viq1xL7b4Mq+pQPRvkUd1751Ld2+8TPjvnYw6b7AIu++jQr1vpvn+r7TXAC/OEADv9sdBr+b9Qi/WscLv/eSDr9UWBG/UBcUv83PFr+sgRm/0CwcvxrRHr9tbiG/qwQkv7eTJr90Gym/x5srv5MULr+7hTC/Ju8yv7dQNb9Vqje/4/s5v0pFPL9uhj6/N79Av4vvQr9TF0W/dTZHv9pMSb9rWku/EF9Nv7NaT78+TVG/mjZTv7MWVb9y7Va/xbpYv5V+Wr/QOFy/YuldvziQX79ALWG/Z8Biv5xJZL/OyGW/6z1nv+OoaL+nCWq/J2Brv1SsbL8f7m2/eiVvv1hScL+rdHG/Z4xyv3+Zc7/nm3S/lZN1v36Adr+WYne/1Dl4vy8Geb+ex3m/F356v5Qpe78Nynu/el98v9XpfL8YaX2/Pt19v0BGfr8cpH6/zPZ+v00+f7+cen+/tqt/v5nRf79D7H+/tPt/v6b/fz+U438/nJp/P8wkfz84gn4//bJ9Pz+3fD8qj3s/8zp6P9S6eD8RD3c/9jd1P9U1cz8ICXE/8bFuP/kwbD+Qhmk/L7NmP1O3Yz+Ek2A/TkhdP0XWWT8DPlY/K4BSP2WdTj9elko/zGtGP2oeQj/5rj0/QB45Pw1tND8ynC8/h6wqP+ueJT8/dCA/bS0bP2HLFT8NTxA/aLkKP2sLBT8ujP4+3dTyPvHy5j5/6No+prfOPohiwj5O67U+KlSpPlGfnD79zo8+beWCPs7Jaz5in1E+MFA3PtPgHD7xVQI+YmjPPXwAmj0k+0g9G6S7PPN3VrtkPfG8u8BjvWddp70Uvdy9A/sIvnN/I7405z2+pC1YviZOcr4SIoa+iQWTvjTPn77VfKy+Mwy5vhp7xb5bx9G+ze7dvlDv6b7HxvW+kLkAvyZ5Br8kIQy/jbARv2YmF7+6gRy/mMEhvxXlJr9K6yu/VtMwv1ucNb+DRTq//c0+v/w0Q7+8eUe/fZtLv4SZT78fc1O/oSdXv2O2Wr/GHl6/MGBhvw96ZL/Ya2e/BzVqvx/VbL+pS2+/N5hxv2K6c7/JsXW/Fn53v/Yeeb8hlHq/Vd17v1n6fL/66n2/Dq9+v3RGf78PsX+/zu5/v/////////////////////8AAAAAAAAAACkAKQApAFIAUgB7AKQAyADeAEH69gELmAEpACkAKQApAHsAewB7AKQApADwAAoBGwEnASkAKQApACkAKQApACkAKQB7AHsAewB7APAA8ADwAAoBCgExAT4BSAFQAXsAewB7AHsAewB7AHsAewDwAPAA8ADwADEBMQExAT4BPgFXAV8BZgFsAfAA8ADwAPAA8ADwAPAA8AAxATEBMQExAVcBVwFXAV8BXwFyAXgBfgGDAQBBoPgBC7gEKAcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcoDxccHyIkJicpKissLS4vLzEyMzQ1Njc3OTo7PD0+Pz9BQkNERUZHRygUISkwNTk9QEJFR0lLTE5QUlVXWVtcXmBiZWdpa2xucHJ1d3l7fH6AKBcnMzxDSU9TV1teYWRmaWtvc3Z5fH6Bg4eLjpGUlpmbn6OmqayusbMjHDFBTllja3J4foSIjZGVmZ+lq7C0ub3Ax83T2Nzh5ejv9fsVITpPYXB9iZSdpq62vcPJz9nj6/P7ESM/Vmp7i5ilsbvFztbe5u36GR83S1tpdYCKkpqhqK60ub7I0Nfe5evw9f8QJEFZboCQn625xM/Z4ury+gspSmeAl6y/0eHx/wkrT26Ko7rP4/YMJ0dje5CktsbW5PH9CSxRcY6owNbr/wcxWn+gv9z3BjNfhqrL6gcvV3ubuNTtBjRhia7Q8AU5apfA5wU7b57K8wU3Z5O74AU8caHO+ARBeq/gBEN/tuoAAAAAAAAAAODg4ODg4ODgoKCgoLm5ubKyqIY9JeDg4ODg4ODg8PDw8M/Pz8bGt5BCKKCgoKCgoKCgubm5ucHBwbe3rIpAJvDw8PDw8PDwz8/Pz8zMzMHBtI9CKLm5ubm5ubm5wcHBwcHBwbe3rIpBJ8/Pz8/Pz8/PzMzMzMnJyby8sI1CKMHBwcHBwcHBwcHBwcLCwri4rYtBJ8zMzMzMzMzMycnJycbGxru7r4xCKABB4vwBC7kpYADAACABgAEgAIAA4ABAAaABQACgAAABYAHAAQgAaADIACgBiAEoAIgA6ABIAagBSACoAAgBaAHIARAAcADQADABkAEwAJAA8ABQAbABUACwABABcAHQARgAeADYADgBmAE4AJgA+ABYAbgBWAC4ABgBeAHYAQQAZADEACQBhAEkAIQA5ABEAaQBRACkAAQBZAHEAQwAbADMACwBjAEsAIwA7ABMAawBTACsAAwBbAHMARQAdADUADQBlAE0AJQA9ABUAbQBVAC0ABQBdAHUARwAfADcADwBnAE8AJwA/ABcAbwBXAC8ABwBfAHcAQEAYQDBACEBgQEhAIEA4QBBAaEBQQChAAEBYQHBAQkAaQDJACkBiQEpAIkA6QBJAakBSQCpAAkBaQHJAREAcQDRADEBkQExAJEA8QBRAbEBUQCxABEBcQHRARkAeQDZADkBmQE5AJkA+QBZAbkBWQC5ABkBeQHZAQUAZQDFACUBhQElAIUA5QBFAaUBRQClAAUBZQHFAQ0AbQDNAC0BjQEtAI0A7QBNAa0BTQCtAA0BbQHNARUAdQDVADUBlQE1AJUA9QBVAbUBVQC1ABUBdQHVAR0AfQDdAD0BnQE9AJ0A/QBdAb0BXQC9AB0BfQHdAQIAYgDCACIBggEiAIIA4gBCAaIBQgCiAAIBYgHCAQoAagDKACoBigEqAIoA6gBKAaoBSgCqAAoBagHKARIAcgDSADIBkgEyAJIA8gBSAbIBUgCyABIBcgHSARoAegDaADoBmgE6AJoA+gBaAboBWgC6ABoBegHaAQYAZgDGACYBhgEmAIYA5gBGAaYBRgCmAAYBZgHGAQ4AbgDOAC4BjgEuAI4A7gBOAa4BTgCuAA4BbgHOARYAdgDWADYBlgE2AJYA9gBWAbYBVgC2ABYBdgHWAR4AfgDeAD4BngE+AJ4A/gBeAb4BXgC+AB4BfgHeAQMAYwDDACMBgwEjAIMA4wBDAaMBQwCjAAMBYwHDAQsAawDLACsBiwErAIsA6wBLAasBSwCrAAsBawHLARMAcwDTADMBkwEzAJMA8wBTAbMBUwCzABMBcwHTARsAewDbADsBmwE7AJsA+wBbAbsBWwC7ABsBewHbAQcAZwDHACcBhwEnAIcA5wBHAacBRwCnAAcBZwHHAQ8AbwDPAC8BjwEvAI8A7wBPAa8BTwCvAA8BbwHPARcAdwDXADcBlwE3AJcA9wBXAbcBVwC3ABcBdwHXAR8AfwDfAD8BnwE/AJ8A/wBfAb8BXwC/AB8BfwHfAQAAgD8AAACAY/p/P791VryL6X8/CnHWvHnNfz/nziC9L6Z/PzpeVr2vc38/E/KFvfk1fz8qr6C9Eu1+PzNlu739mH4/BBPWvbw5fj9zt/C9Vc99P6ioBb7LWX0/u+8SviXZfD9cMCC+Z018P/VpLb6Ytns/85s6vr4Uez/CxUe+4md6P83mVL4JsHk/gv5hvjzteD9NDG++hB94P5wPfL7qRnc/7oOEvndjdj8++oq+NnV1P3Vqkb4wfHQ/TNSXvnF4cz96N56+A2pyP7eTpL70UHE/vOiqvk8tcD9BNrG+If9uPwF8t752xm0/tLm9vl6DbD8V78O+5zVrP94byr4e3mk/yT/QvhJ8aD+SWta+1A9nP/Nr3L50mWU/qnPivgEZZD9xcei+jY5iPwdl7r4o+mA/J070vuZbXz+QLPq+17NdPwAAAL8PAlw/G+QCv6BGWj93wgW/noFYP/aaCL8ds1Y/d20LvzHbVD/aOQ6/7/lSPwAAEb9sD1E/yr8Tv70bTz8YeRa/+B5NP80rGb80GUs/ytcbv4gKST/xfB6/CvNGPyQbIb/R0kQ/RrIjv/epQj86Qia/k3hAP+PKKL+9Pj4/JUwrv4/8Oz/jxS2/IrI5PwE4ML+QXzc/ZaIyv/MENT/zBDW/ZaIyP5BfN78BODA/IrI5v+PFLT+P/Du/JUwrP70+Pr/jyig/k3hAvzpCJj/3qUK/RrIjP9HSRL8kGyE/CvNGv/F8Hj+ICkm/ytcbPzQZS7/NKxk/+B5Nvxh5Fj+9G0+/yr8TP2wPUb8AABE/7/lSv9o5Dj8x21S/d20LPx2zVr/2mgg/noFYv3fCBT+gRlq/G+QCPw8CXL8AAAA/17Ndv5As+j7mW1+/J070Pij6YL8HZe4+jY5iv3Fx6D4BGWS/qnPiPnSZZb/za9w+1A9nv5Ja1j4SfGi/yT/QPh7eab/eG8o+5zVrvxXvwz5eg2y/tLm9PnbGbb8BfLc+If9uv0E2sT5PLXC/vOiqPvRQcb+3k6Q+A2pyv3o3nj5xeHO/TNSXPjB8dL91apE+NnV1vz76ij53Y3a/7oOEPupGd7+cD3w+hB94v00Mbz487Xi/gv5hPgmweb/N5lQ+4md6v8LFRz6+FHu/85s6Ppi2e7/1aS0+Z018v1wwID4l2Xy/u+8SPstZfb+oqAU+Vc99v3O38D28OX6/BBPWPf2Yfr8zZbs9Eu1+vyqvoD35NX+/E/KFPa9zf786XlY9L6Z/v+fOID15zX+/CnHWPIvpf7+/dVY8Y/p/vwAwjSQAAIC/v3VWvGP6f78Kcda8i+l/v+fOIL15zX+/Ol5WvS+mf78T8oW9r3N/vyqvoL35NX+/M2W7vRLtfr8EE9a9/Zh+v3O38L28OX6/qKgFvlXPfb+77xK+y1l9v1wwIL4l2Xy/9WktvmdNfL/zmzq+mLZ7v8LFR76+FHu/zeZUvuJner+C/mG+CbB5v00Mb7487Xi/nA98voQfeL/ug4S+6kZ3vz76ir53Y3a/dWqRvjZ1db9M1Je+MHx0v3o3nr5xeHO/t5OkvgNqcr+86Kq+9FBxv0E2sb5PLXC/AXy3viH/br+0ub2+dsZtvxXvw75eg2y/3hvKvuc1a7/JP9C+Ht5pv5Ja1r4SfGi/82vcvtQPZ7+qc+K+dJllv3Fx6L4BGWS/B2Xuvo2OYr8nTvS+KPpgv5As+r7mW1+/AAAAv9ezXb8b5AK/DwJcv3fCBb+gRlq/9poIv56BWL93bQu/HbNWv9o5Dr8x21S/AAARv+/5Ur/KvxO/bA9Rvxh5Fr+9G0+/zSsZv/geTb/K1xu/NBlLv/F8Hr+ICkm/JBshvwrzRr9GsiO/0dJEvzpCJr/3qUK/48oov5N4QL8lTCu/vT4+v+PFLb+P/Du/ATgwvyKyOb9lojK/kF83v/MENb/zBDW/kF83v2WiMr8isjm/ATgwv4/8O7/jxS2/vT4+vyVMK7+TeEC/48oov/epQr86Qia/0dJEv0ayI78K80a/JBshv4gKSb/xfB6/NBlLv8rXG7/4Hk2/zSsZv70bT78YeRa/bA9Rv8q/E7/v+VK/AAARvzHbVL/aOQ6/HbNWv3dtC7+egVi/9poIv6BGWr93wgW/DwJcvxvkAr/Xs12/AAAAv+ZbX7+QLPq+KPpgvydO9L6NjmK/B2XuvgEZZL9xcei+dJllv6pz4r7UD2e/82vcvhJ8aL+SWta+Ht5pv8k/0L7nNWu/3hvKvl6DbL8V78O+dsZtv7S5vb4h/26/AXy3vk8tcL9BNrG+9FBxv7zoqr4DanK/t5OkvnF4c796N56+MHx0v0zUl742dXW/dWqRvndjdr8++oq+6kZ3v+6DhL6EH3i/nA98vjzteL9NDG++CbB5v4L+Yb7iZ3q/zeZUvr4Ue7/CxUe+mLZ7v/ObOr5nTXy/9WktviXZfL9cMCC+y1l9v7vvEr5Vz32/qKgFvrw5fr9zt/C9/Zh+vwQT1r0S7X6/M2W7vfk1f78qr6C9r3N/vxPyhb0vpn+/Ol5WvXnNf7/nziC9i+l/vwpx1rxj+n+/v3VWvAAAgL8AMA2lY/p/v791VjyL6X+/CnHWPHnNf7/nziA9L6Z/vzpeVj2vc3+/E/KFPfk1f78qr6A9Eu1+vzNluz39mH6/BBPWPbw5fr9zt/A9Vc99v6ioBT7LWX2/u+8SPiXZfL9cMCA+Z018v/VpLT6Ytnu/85s6Pr4Ue7/CxUc+4md6v83mVD4JsHm/gv5hPjzteL9NDG8+hB94v5wPfD7qRne/7oOEPndjdr8++oo+NnV1v3VqkT4wfHS/TNSXPnF4c796N54+A2pyv7eTpD70UHG/vOiqPk8tcL9BNrE+If9uvwF8tz52xm2/tLm9Pl6DbL8V78M+5zVrv94byj4e3mm/yT/QPhJ8aL+SWtY+1A9nv/Nr3D50mWW/qnPiPgEZZL9xceg+jY5ivwdl7j4o+mC/J070PuZbX7+QLPo+17NdvwAAAD8PAly/G+QCP6BGWr93wgU/noFYv/aaCD8ds1a/d20LPzHbVL/aOQ4/7/lSvwAAET9sD1G/yr8TP70bT78YeRY/+B5Nv80rGT80GUu/ytcbP4gKSb/xfB4/CvNGvyQbIT/R0kS/RrIjP/epQr86QiY/k3hAv+PKKD+9Pj6/JUwrP4/8O7/jxS0/IrI5vwE4MD+QXze/ZaIyP/MENb/zBDU/ZaIyv5BfNz8BODC/IrI5P+PFLb+P/Ds/JUwrv70+Pj/jyii/k3hAPzpCJr/3qUI/RrIjv9HSRD8kGyG/CvNGP/F8Hr+ICkk/ytcbvzQZSz/NKxm/+B5NPxh5Fr+9G08/yr8Tv2wPUT8AABG/7/lSP9o5Dr8x21Q/d20Lvx2zVj/2mgi/noFYP3fCBb+gRlo/G+QCvw8CXD8AAAC/17NdP5As+r7mW18/J070vij6YD8HZe6+jY5iP3Fx6L4BGWQ/qnPivnSZZT/za9y+1A9nP5Ja1r4SfGg/yT/Qvh7eaT/eG8q+5zVrPxXvw75eg2w/tLm9vnbGbT8BfLe+If9uP0E2sb5PLXA/vOiqvvRQcT+3k6S+A2pyP3o3nr5xeHM/TNSXvjB8dD91apG+NnV1Pz76ir53Y3Y/7oOEvupGdz+cD3y+hB94P00Mb7487Xg/gv5hvgmweT/N5lS+4md6P8LFR76+FHs/85s6vpi2ez/1aS2+Z018P1wwIL4l2Xw/u+8SvstZfT+oqAW+Vc99P3O38L28OX4/BBPWvf2Yfj8zZbu9Eu1+PyqvoL35NX8/E/KFva9zfz86Xla9L6Z/P+fOIL15zX8/CnHWvIvpfz+/dVa8Y/p/PwDIU6UAAIA/v3VWPGP6fz8KcdY8i+l/P+fOID15zX8/Ol5WPS+mfz8T8oU9r3N/PyqvoD35NX8/M2W7PRLtfj8EE9Y9/Zh+P3O38D28OX4/qKgFPlXPfT+77xI+y1l9P1wwID4l2Xw/9WktPmdNfD/zmzo+mLZ7P8LFRz6+FHs/zeZUPuJnej+C/mE+CbB5P00Mbz487Xg/nA98PoQfeD/ug4Q+6kZ3Pz76ij53Y3Y/dWqRPjZ1dT9M1Jc+MHx0P3o3nj5xeHM/t5OkPgNqcj+86Ko+9FBxP0E2sT5PLXA/AXy3PiH/bj+0ub0+dsZtPxXvwz5eg2w/3hvKPuc1az/JP9A+Ht5pP5Ja1j4SfGg/82vcPtQPZz+qc+I+dJllP3Fx6D4BGWQ/B2XuPo2OYj8nTvQ+KPpgP5As+j7mW18/AAAAP9ezXT8b5AI/DwJcP3fCBT+gRlo/9poIP56BWD93bQs/HbNWP9o5Dj8x21Q/AAARP+/5Uj/KvxM/bA9RPxh5Fj+9G08/zSsZP/geTT/K1xs/NBlLP/F8Hj+ICkk/JBshPwrzRj9GsiM/0dJEPzpCJj/3qUI/48ooP5N4QD8lTCs/vT4+P+PFLT+P/Ds/ATgwPyKyOT9lojI/kF83P/MENT/zBDU/kF83P2WiMj8isjk/ATgwP4/8Oz/jxS0/vT4+PyVMKz+TeEA/48ooP/epQj86QiY/0dJEP0ayIz8K80Y/JBshP4gKST/xfB4/NBlLP8rXGz/4Hk0/zSsZP70bTz8YeRY/bA9RP8q/Ez/v+VI/AAARPzHbVD/aOQ4/HbNWP3dtCz+egVg/9poIP6BGWj93wgU/DwJcPxvkAj/Xs10/AAAAP+ZbXz+QLPo+KPpgPydO9D6NjmI/B2XuPgEZZD9xceg+dJllP6pz4j7UD2c/82vcPhJ8aD+SWtY+Ht5pP8k/0D7nNWs/3hvKPl6DbD8V78M+dsZtP7S5vT4h/24/AXy3Pk8tcD9BNrE+9FBxP7zoqj4DanI/t5OkPnF4cz96N54+MHx0P0zUlz42dXU/dWqRPndjdj8++oo+6kZ3P+6DhD6EH3g/nA98PjzteD9NDG8+CbB5P4L+YT7iZ3o/zeZUPr4Uez/CxUc+mLZ7P/ObOj5nTXw/9WktPiXZfD9cMCA+y1l9P7vvEj5Vz30/qKgFPrw5fj9zt/A9/Zh+PwQT1j0S7X4/M2W7Pfk1fz8qr6A9r3N/PxPyhT0vpn8/Ol5WPXnNfz/nziA9i+l/Pwpx1jxj+n8/v3VWPAAAMABgAJAAwAAQAEAAcACgANAAIABQAIAAsADgAAQANABkAJQAxAAUAEQAdACkANQAJABUAIQAtADkAAgAOABoAJgAyAAYAEgAeACoANgAKABYAIgAuADoAAwAPABsAJwAzAAcAEwAfACsANwALABcAIwAvADsAAEAMQBhAJEAwQARAEEAcQChANEAIQBRAIEAsQDhAAUANQBlAJUAxQAVAEUAdQClANUAJQBVAIUAtQDlAAkAOQBpAJkAyQAZAEkAeQCpANkAKQBZAIkAuQDpAA0APQBtAJ0AzQAdAE0AfQCtAN0ALQBdAI0AvQDtAAIAMgBiAJIAwgASAEIAcgCiANIAIgBSAIIAsgDiAAYANgBmAJYAxgAWAEYAdgCmANYAJgBWAIYAtgDmAAoAOgBqAJoAygAaAEoAegCqANoAKgBaAIoAugDqAA4APgBuAJ4AzgAeAE4AfgCuAN4ALgBeAI4AvgDuAAMAMwBjAJMAwwATAEMAcwCjANMAIwBTAIMAswDjAAcANwBnAJcAxwAXAEcAdwCnANcAJwBXAIcAtwDnAAsAOwBrAJsAywAbAEsAewCrANsAKwBbAIsAuwDrAA8APwBvAJ8AzwAfAE8AfwCvAN8ALwBfAI8AvwDvAPAAAACJiIg7AQAAAAUAMAADABAABAAEAAQAAQBBrKYCCwYgkQAAIIIAQcKmAguJAhgAMABIAGAACAAgADgAUABoABAAKABAAFgAcAAEABwANABMAGQADAAkADwAVABsABQALABEAFwAdAABABkAMQBJAGEACQAhADkAUQBpABEAKQBBAFkAcQAFAB0ANQBNAGUADQAlAD0AVQBtABUALQBFAF0AdQACABoAMgBKAGIACgAiADoAUgBqABIAKgBCAFoAcgAGAB4ANgBOAGYADgAmAD4AVgBuABYALgBGAF4AdgADABsAMwBLAGMACwAjADsAUwBrABMAKwBDAFsAcwAHAB8ANwBPAGcADwAnAD8AVwBvABcALwBHAF8AdwB4AAAAiIgIPAIAAAAFABgAAwAIAAIABAAEAAEAQdyoAgsGQJMAACCCAEHyqAILjQEMABgAJAAwAAQAEAAcACgANAAIABQAIAAsADgAAQANABkAJQAxAAUAEQAdACkANQAJABUAIQAtADkAAgAOABoAJgAyAAYAEgAeACoANgAKABYAIgAuADoAAwAPABsAJwAzAAcAEwAfACsANwALABcAIwAvADsAPAAAAImIiDwDAAAABQAMAAMABAAEAAEAQZSqAguQAXCUAAAgggAAAAAAAJWLAAA3mAAA/6UAAAS1AABnxQAARdcAAMHqAAD//wAAAADOQAAAyEAAALhAAACqQAAAokAAAJpAAACQQAAAjEAAAJxAAACWQAAAkkAAAI5AAACcQAAAlEAAAIpAAACQQAAAjEAAAJRAAACYQAAAjkAAAHBAAABwQAAAcEAAAHBAAABwQABBsKsCC/ICSH9BgUKAQYBAgD6AQIBAgFxOXE9cTlpPdClzKHIohBqEGpERoQywCrELGLMwijaH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";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}var binary=tryParseAsDataURI(file);if(binary){return binary}if(readBinary){return readBinary(file)}else{throw"sync fetching of the wasm failed: you can preload it to Module['wasmBinary'] manually, or emcc.py will do that for you when generating HTML (but not JS)"}}catch(err){abort(err)}}function instantiateSync(file,info){var instance;var module;var binary;try{binary=getBinary(file);module=new WebAssembly.Module(binary);instance=new WebAssembly.Instance(module,info)}catch(e){var str=e.toString();err("failed to compile wasm module: "+str);if(str.includes("imported Memory")||str.includes("memory import")){err("Memory size incompatibility issues may be due to changing INITIAL_MEMORY at runtime to something too large. Use ALLOW_MEMORY_GROWTH to allow any size memory (and also make sure not to set INITIAL_MEMORY at runtime to something smaller than it was at compile time).")}throw e}return[instance,module]}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["g"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["r"];addOnInit(Module["asm"]["h"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}var result=instantiateSync(wasmBinaryFile,info);receiveInstance(result[0]);return Module["asm"]}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback(Module);continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){wasmTable.get(func)()}else{wasmTable.get(func)(callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}function _abort(){abort("")}function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function abortOnCannotGrowMemory(requestedSize){abort("OOM")}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;abortOnCannotGrowMemory(requestedSize)}var SYSCALLS={mappings:{},buffers:[null,[],[]],printChar:function(stream,curr){var buffer=SYSCALLS.buffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer,0));buffer.length=0}else{buffer.push(curr)}},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},get64:function(low,high){return low}};function _fd_close(fd){return 0}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){}function _fd_write(fd,iov,iovcnt,pnum){var num=0;for(var i=0;i>2];var len=HEAP32[iov+(i*8+4)>>2];for(var j=0;j>2]=num;return 0}var ASSERTIONS=false;function intArrayToString(array){var ret=[];for(var i=0;i255){if(ASSERTIONS){assert(false,"Character code "+chr+" ("+String.fromCharCode(chr)+") at offset "+i+" not in 0x00-0xFF.")}chr&=255}ret.push(String.fromCharCode(chr))}return ret.join("")}var decodeBase64=typeof atob==="function"?atob:function(input){var keyStr="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";var output="";var chr1,chr2,chr3;var enc1,enc2,enc3,enc4;var i=0;input=input.replace(/[^A-Za-z0-9\+\/\=]/g,"");do{enc1=keyStr.indexOf(input.charAt(i++));enc2=keyStr.indexOf(input.charAt(i++));enc3=keyStr.indexOf(input.charAt(i++));enc4=keyStr.indexOf(input.charAt(i++));chr1=enc1<<2|enc2>>4;chr2=(enc2&15)<<4|enc3>>2;chr3=(enc3&3)<<6|enc4;output=output+String.fromCharCode(chr1);if(enc3!==64){output=output+String.fromCharCode(chr2)}if(enc4!==64){output=output+String.fromCharCode(chr3)}}while(i0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run();null;const OggOpusEncoder=function(config,Module){if(!Module){throw new Error("Module with exports required to initialize an encoder instance")}this.config=Object.assign({encoderApplication:2049,encoderFrameSize:20,encoderSampleRate:48e3,maxFramesPerPage:40,numberOfChannels:1,originalSampleRate:44100,resampleQuality:3,serial:Math.floor(Math.random()*4294967296)},config);this._opus_encoder_create=Module._opus_encoder_create;this._opus_encoder_destroy=Module._opus_encoder_destroy;this._opus_encoder_ctl=Module._opus_encoder_ctl;this._speex_resampler_process_interleaved_float=Module._speex_resampler_process_interleaved_float;this._speex_resampler_init=Module._speex_resampler_init;this._speex_resampler_destroy=Module._speex_resampler_destroy;this._opus_encode_float=Module._opus_encode_float;this._free=Module._free;this._malloc=Module._malloc;this.HEAPU8=Module.HEAPU8;this.HEAP32=Module.HEAP32;this.HEAPF32=Module.HEAPF32;this.pageIndex=0;this.granulePosition=0;this.segmentData=new Uint8Array(65025);this.segmentDataIndex=0;this.segmentTable=new Uint8Array(255);this.segmentTableIndex=0;this.framesInPage=0;this.initChecksumTable();this.initCodec();this.initResampler();if(this.config.numberOfChannels===1){this.interleave=function(buffers){return buffers[0]}}};OggOpusEncoder.prototype.encode=function(buffers){if(!this.bufferLength){this.bufferLength=buffers[0].length;this.interleavedBuffers=new Float32Array(this.bufferLength*this.config.numberOfChannels)}var samples=this.interleave(buffers);var sampleIndex=0;var exportPages=[];while(sampleIndex=this.config.maxFramesPerPage){exportPages.push(this.generatePage())}}}return exportPages};OggOpusEncoder.prototype.destroy=function(){if(this.encoder){this._free(this.encoderSamplesPerChannelPointer);delete this.encoderSamplesPerChannelPointer;this._free(this.encoderBufferPointer);delete this.encoderBufferPointer;this._free(this.encoderOutputPointer);delete this.encoderOutputPointer;this._free(this.resampleSamplesPerChannelPointer);delete this.resampleSamplesPerChannelPointer;this._free(this.resampleBufferPointer);delete this.resampleBufferPointer;this._speex_resampler_destroy(this.resampler);delete this.resampler;this._opus_encoder_destroy(this.encoder);delete this.encoder}};OggOpusEncoder.prototype.flush=function(){var exportPage;if(this.framesInPage){exportPage=this.generatePage()}this.resampleBufferIndex=0;return exportPage};OggOpusEncoder.prototype.encodeFinalFrame=function(){var exportPages=[];if(this.resampleBufferIndex>0){const dataToFill=(this.resampleBufferLength-this.resampleBufferIndex)/this.config.numberOfChannels;const numBuffers=Math.ceil(dataToFill/this.bufferLength);for(var i=0;i>>24&255^data[i]]}return checksum>>>0};OggOpusEncoder.prototype.generateCommentPage=function(){var segmentDataView=new DataView(this.segmentData.buffer);segmentDataView.setUint32(0,1937076303,true);segmentDataView.setUint32(4,1936154964,true);segmentDataView.setUint32(8,10,true);segmentDataView.setUint32(12,1868784978,true);segmentDataView.setUint32(16,1919247474,true);segmentDataView.setUint16(20,21322,true);segmentDataView.setUint32(22,0,true);this.segmentTableIndex=1;this.segmentDataIndex=this.segmentTable[0]=26;this.headerType=0;return this.generatePage()};OggOpusEncoder.prototype.generateIdPage=function(){var segmentDataView=new DataView(this.segmentData.buffer);segmentDataView.setUint32(0,1937076303,true);segmentDataView.setUint32(4,1684104520,true);segmentDataView.setUint8(8,1,true);segmentDataView.setUint8(9,this.config.numberOfChannels,true);segmentDataView.setUint16(10,3840,true);segmentDataView.setUint32(12,this.config.originalSampleRateOverride||this.config.originalSampleRate,true);segmentDataView.setUint16(16,0,true);segmentDataView.setUint8(18,0,true);this.segmentTableIndex=1;this.segmentDataIndex=this.segmentTable[0]=19;this.headerType=2;return this.generatePage()};OggOpusEncoder.prototype.generatePage=function(){var granulePosition=this.lastPositiveGranulePosition===this.granulePosition?-1:this.granulePosition;var pageBuffer=new ArrayBuffer(27+this.segmentTableIndex+this.segmentDataIndex);var pageBufferView=new DataView(pageBuffer);var page=new Uint8Array(pageBuffer);pageBufferView.setUint32(0,1399285583,true);pageBufferView.setUint8(4,0,true);pageBufferView.setUint8(5,this.headerType,true);pageBufferView.setUint32(6,granulePosition,true);if(granulePosition<0){pageBufferView.setInt32(10,Math.ceil(granulePosition/4294967297)-1,true)}else{pageBufferView.setInt32(10,Math.floor(granulePosition/4294967296),true)}pageBufferView.setUint32(14,this.config.serial,true);pageBufferView.setUint32(18,this.pageIndex++,true);pageBufferView.setUint8(26,this.segmentTableIndex,true);page.set(this.segmentTable.subarray(0,this.segmentTableIndex),27);page.set(this.segmentData.subarray(0,this.segmentDataIndex),27+this.segmentTableIndex);pageBufferView.setUint32(22,this.getChecksum(page),true);var exportPage={message:"page",page:page,samplePosition:this.granulePosition};this.segmentTableIndex=0;this.segmentDataIndex=0;this.framesInPage=0;if(granulePosition>0){this.lastPositiveGranulePosition=granulePosition}return exportPage};OggOpusEncoder.prototype.initChecksumTable=function(){this.checksumTable=[];for(var i=0;i<256;i++){var r=i<<24;for(var j=0;j<8;j++){r=(r&2147483648)!=0?r<<1^79764919:r<<1}this.checksumTable[i]=r&4294967295}};OggOpusEncoder.prototype.setOpusControl=function(control,value){var location=this._malloc(4);this.HEAP32[location>>2]=value;this._opus_encoder_ctl(this.encoder,control,location);this._free(location)};OggOpusEncoder.prototype.initCodec=function(){var errLocation=this._malloc(4);this.encoder=this._opus_encoder_create(this.config.encoderSampleRate,this.config.numberOfChannels,this.config.encoderApplication,errLocation);this._free(errLocation);if(this.config.encoderBitRate){this.setOpusControl(4002,this.config.encoderBitRate)}if(this.config.encoderComplexity){this.setOpusControl(4010,this.config.encoderComplexity)}this.encoderSamplesPerChannel=this.config.encoderSampleRate*this.config.encoderFrameSize/1e3;this.encoderSamplesPerChannelPointer=this._malloc(4);this.HEAP32[this.encoderSamplesPerChannelPointer>>2]=this.encoderSamplesPerChannel;this.encoderBufferLength=this.encoderSamplesPerChannel*this.config.numberOfChannels;this.encoderBufferPointer=this._malloc(this.encoderBufferLength*4);this.encoderBuffer=this.HEAPF32.subarray(this.encoderBufferPointer>>2,(this.encoderBufferPointer>>2)+this.encoderBufferLength);this.encoderOutputMaxLength=4e3;this.encoderOutputPointer=this._malloc(this.encoderOutputMaxLength);this.encoderOutputBuffer=this.HEAPU8.subarray(this.encoderOutputPointer,this.encoderOutputPointer+this.encoderOutputMaxLength)};OggOpusEncoder.prototype.initResampler=function(){var errLocation=this._malloc(4);this.resampler=this._speex_resampler_init(this.config.numberOfChannels,this.config.originalSampleRate,this.config.encoderSampleRate,this.config.resampleQuality,errLocation);this._free(errLocation);this.resampleBufferIndex=0;this.resampleSamplesPerChannel=this.config.originalSampleRate*this.config.encoderFrameSize/1e3;this.resampleSamplesPerChannelPointer=this._malloc(4);this.HEAP32[this.resampleSamplesPerChannelPointer>>2]=this.resampleSamplesPerChannel;this.resampleBufferLength=this.resampleSamplesPerChannel*this.config.numberOfChannels;this.resampleBufferPointer=this._malloc(this.resampleBufferLength*4);this.resampleBuffer=this.HEAPF32.subarray(this.resampleBufferPointer>>2,(this.resampleBufferPointer>>2)+this.resampleBufferLength)};OggOpusEncoder.prototype.interleave=function(buffers){for(var i=0;i=0){if(this.segmentTableIndex===255){exportPages.push(this.generatePage());this.headerType=1}var segmentLength=Math.min(packetLength,255);this.segmentTable[this.segmentTableIndex++]=segmentLength;this.segmentData.set(this.encoderOutputBuffer.subarray(packetIndex,packetIndex+segmentLength),this.segmentDataIndex);this.segmentDataIndex+=segmentLength;packetIndex+=segmentLength;packetLength-=255}this.granulePosition+=48*this.config.encoderFrameSize;if(this.segmentTableIndex===255){exportPages.push(this.generatePage());this.headerType=0}return exportPages};if(typeof registerProcessor==="function"){class EncoderWorklet extends AudioWorkletProcessor{constructor(){super();this.continueProcess=true;this.port.onmessage=(({data:data})=>{if(this.encoder){switch(data["command"]){case"getHeaderPages":this.postPage(this.encoder.generateIdPage());this.postPage(this.encoder.generateCommentPage());break;case"done":this.encoder.encodeFinalFrame().forEach(pageData=>this.postPage(pageData));this.encoder.destroy();delete this.encoder;this.port.postMessage({message:"done"});break;case"flush":this.postPage(this.encoder.flush());this.port.postMessage({message:"flushed"});break;default:}}switch(data["command"]){case"close":this.continueProcess=false;break;case"init":this.encoder=new OggOpusEncoder(data,Module);this.port.postMessage({message:"ready"});break;default:}})}process(inputs){if(this.encoder&&inputs[0]&&inputs[0].length&&inputs[0][0]&&inputs[0][0].length){this.encoder.encode(inputs[0]).forEach(pageData=>this.postPage(pageData))}return this.continueProcess}postPage(pageData){if(pageData){this.port.postMessage(pageData,[pageData.page.buffer])}}}registerProcessor("encoder-worklet",EncoderWorklet)}else{var encoder;var postPageGlobal=pageData=>{if(pageData){postMessage(pageData,[pageData.page.buffer])}};onmessage=(({data:data})=>{if(encoder){switch(data["command"]){case"encode":encoder.encode(data["buffers"]).forEach(pageData=>postPageGlobal(pageData));break;case"getHeaderPages":postPageGlobal(encoder.generateIdPage());postPageGlobal(encoder.generateCommentPage());break;case"done":encoder.encodeFinalFrame().forEach(pageData=>postPageGlobal(pageData));encoder.destroy();encoder=null;postMessage({message:"done"});break;case"flush":postPageGlobal(encoder.flush());postMessage({message:"flushed"});break;default:}}switch(data["command"]){case"close":close();break;case"init":encoder=new OggOpusEncoder(data,Module);postMessage({message:"ready"});break;default:}})}var module=module||{};module.exports={Module:Module,OggOpusEncoder:OggOpusEncoder}; diff --git a/main/manager-web/public/index.html b/main/manager-web/public/index.html index 23f19907..e3ae5a53 100644 --- a/main/manager-web/public/index.html +++ b/main/manager-web/public/index.html @@ -9,7 +9,7 @@ <%= process.env.VUE_APP_TITLE %> - <% if (htmlWebpackPlugin.options.cdn) { %> + <% if (htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %> <% for (var i in htmlWebpackPlugin.options.cdn.css) { %> <% } %> @@ -23,7 +23,7 @@
- <% if (htmlWebpackPlugin.options.cdn) { %> + <% if (htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %> <% for (var i in htmlWebpackPlugin.options.cdn.js) { %> <% } %> diff --git a/main/manager-web/public/offline.html b/main/manager-web/public/offline.html new file mode 100644 index 00000000..b454b600 --- /dev/null +++ b/main/manager-web/public/offline.html @@ -0,0 +1,70 @@ + + + + + + 离线模式 - 小智控制台 + + + +
+
📶
+

您当前处于离线模式

+

看起来您的网络连接有问题,无法连接到小智控制台服务器。

+

部分已缓存的内容和静态资源可能仍然可用,但功能可能受到限制。

+ +
+ + + + \ No newline at end of file diff --git a/main/manager-web/src/App.vue b/main/manager-web/src/App.vue index 4e54377e..0eefc0bc 100644 --- a/main/manager-web/src/App.vue +++ b/main/manager-web/src/App.vue @@ -1,6 +1,7 @@ @@ -45,4 +46,118 @@ nav { } \ No newline at end of file diff --git a/main/manager-web/src/apis/api.js b/main/manager-web/src/apis/api.js index 2b5372dd..0a3e8d18 100755 --- a/main/manager-web/src/apis/api.js +++ b/main/manager-web/src/apis/api.js @@ -2,17 +2,17 @@ import admin from './module/admin.js' import agent from './module/agent.js' import device from './module/device.js' +import dict from './module/dict.js' import model from './module/model.js' +import ota from './module/ota.js' +import timbre from "./module/timbre.js" import user from './module/user.js' -import timbre from "./module/timbre.js"; - /** * 接口地址 * 开发时自动读取使用.env.development文件 * 编译时自动读取使用.env.production文件 */ -// const DEV_API_SERVICE = process.env.VUE_APP_API_BASE_URL -const DEV_API_SERVICE = 'https://2662r3426b.vicp.fun/xiaozhi' +const DEV_API_SERVICE = process.env.VUE_APP_API_BASE_URL /** * 根据开发环境返回接口url @@ -32,4 +32,6 @@ export default { device, model, timbre, + ota, + dict } diff --git a/main/manager-web/src/apis/httpRequest.js b/main/manager-web/src/apis/httpRequest.js index 43cdf92d..245bf6cc 100755 --- a/main/manager-web/src/apis/httpRequest.js +++ b/main/manager-web/src/apis/httpRequest.js @@ -20,6 +20,7 @@ function sendRequest() { return { _sucCallback: null, _failCallback: null, + _networkFailCallback: null, _method: 'GET', _data: {}, _header: { 'content-type': 'application/json; charset=utf-8' }, @@ -36,7 +37,7 @@ function sendRequest() { headers: this._header, responseType: this._responseType }).then((res) => { - const error = httpHandlerError(res, this._failCallback); + const error = httpHandlerError(res, this._failCallback, this._networkFailCallback); if (error) { return } @@ -47,7 +48,7 @@ function sendRequest() { }).catch((res) => { // 打印失败响应 console.log('catch', res) - httpHandlerError(res, this._failCallback) + httpHandlerError(res, this._failCallback, this._networkFailCallback) }) return this }, @@ -59,6 +60,10 @@ function sendRequest() { this._failCallback = callback return this }, + 'networkFail'(callback) { + this._networkFailCallback = callback + return this + }, 'url'(url) { if (url) { url = url.replaceAll('$', '/') @@ -95,11 +100,11 @@ function sendRequest() { /** * Info 请求完成后返回信息 - * callBack 回调函数 - * errTip 自定义错误信息 + * failCallback 回调函数 + * networkFailCallback 回调函数 */ // 在错误处理函数中添加日志 -function httpHandlerError(info, callBack) { +function httpHandlerError(info, failCallback, networkFailCallback) { /** 请求成功,退出该函数 可以根据项目需求来判断是否请求成功。这里判断的是status为200的时候是成功 */ let networkError = false @@ -111,12 +116,16 @@ function httpHandlerError(info, callBack) { goToPage(Constant.PAGE.LOGIN, true); return true } else { - showDanger(info.data.msg) + if (failCallback) { + failCallback(info) + } else { + showDanger(info.data.msg) + } return true } } - if (callBack) { - callBack(info) + if (networkFailCallback) { + networkFailCallback(info) } else { showDanger(`网络请求出现了错误【${info.status}】`) } diff --git a/main/manager-web/src/apis/module/admin.js b/main/manager-web/src/apis/module/admin.js index 76fabb87..27c1bc3e 100644 --- a/main/manager-web/src/apis/module/admin.js +++ b/main/manager-web/src/apis/module/admin.js @@ -18,7 +18,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('请求失败:', err) RequestService.reAjaxFun(() => { this.getUserList(callback) @@ -34,7 +34,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('删除失败:', err) RequestService.reAjaxFun(() => { this.deleteUser(id, callback) @@ -50,17 +50,19 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('重置密码失败:', err) RequestService.reAjaxFun(() => { this.resetUserPassword(id, callback) }) }).send() }, + // 获取参数列表 getParamsList(params, callback) { const queryParams = new URLSearchParams({ page: params.page, - limit: params.limit + limit: params.limit, + paramCode: params.paramCode || '' }).toString(); RequestService.sendRequest() @@ -70,13 +72,14 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('获取参数列表失败:', err) RequestService.reAjaxFun(() => { this.getParamsList(params, callback) }) }).send() }, + // 保存 addParam(data, callback) { RequestService.sendRequest() .url(`${getServiceUrl()}/admin/params`) @@ -86,13 +89,14 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('添加参数失败:', err) RequestService.reAjaxFun(() => { this.addParam(data, callback) }) }).send() }, + // 修改 updateParam(data, callback) { RequestService.sendRequest() .url(`${getServiceUrl()}/admin/params`) @@ -102,43 +106,61 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('更新参数失败:', err) RequestService.reAjaxFun(() => { this.updateParam(data, callback) }) }).send() }, - deleteParam(id, callback) { + // 删除 + deleteParam(ids, callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/admin/params`) - .method('DELETE') - .data({ ids: [id] }) + .url(`${getServiceUrl()}/admin/params/delete`) + .method('POST') + .data(ids) .success((res) => { RequestService.clearRequestTime() - callback(res) + callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('删除参数失败:', err) RequestService.reAjaxFun(() => { - this.deleteParam(id, callback) + this.deleteParam(ids, callback) }) }).send() }, - batchDeleteParams(ids, callback) { + // 获取ws服务端列表 + getWsServerList(params, callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/admin/params`) - .method('DELETE') - .data({ ids }) + .url(`${getServiceUrl()}/admin/server/server-list`) + .method('GET') .success((res) => { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { - console.error('批量删除参数失败:', err) + .networkFail((err) => { + console.error('获取ws服务端列表失败:', err) RequestService.reAjaxFun(() => { - this.batchDeleteParams(ids, callback) + this.getWsServerList(params, callback) }) - }).send() + }).send(); + }, + // 发送ws服务器动作指令 + sendWsServerAction(data, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/admin/server/emit-action`) + .method('POST') + .data(data) + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .networkFail((err) => { + RequestService.reAjaxFun(() => { + this.sendWsServerAction(data, callback) + }) + }).send(); } + } diff --git a/main/manager-web/src/apis/module/agent.js b/main/manager-web/src/apis/module/agent.js index 87323f9c..7f33d320 100644 --- a/main/manager-web/src/apis/module/agent.js +++ b/main/manager-web/src/apis/module/agent.js @@ -12,7 +12,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .networkFail(() => { RequestService.reAjaxFun(() => { this.getAgentList(callback); }); @@ -28,7 +28,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .networkFail(() => { RequestService.reAjaxFun(() => { this.addAgent(agentName, callback); }); @@ -43,25 +43,25 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .networkFail(() => { RequestService.reAjaxFun(() => { this.deleteAgent(agentId, callback); }); }).send(); }, // 获取智能体配置 - getDeviceConfig(deviceId, callback) { + getDeviceConfig(agentId, callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/agent/${deviceId}`) + .url(`${getServiceUrl()}/agent/${agentId}`) .method('GET') .success((res) => { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('获取配置失败:', err); RequestService.reAjaxFun(() => { - this.getDeviceConfig(deviceId, callback); + this.getDeviceConfig(agentId, callback); }); }).send(); }, @@ -75,7 +75,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .networkFail(() => { RequestService.reAjaxFun(() => { this.updateAgentConfig(agentId, configData, callback); }); @@ -90,11 +90,57 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('获取模板失败:', err); RequestService.reAjaxFun(() => { this.getAgentTemplate(callback); }); }).send(); }, + // 获取智能体会话列表 + getAgentSessions(agentId, params, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/agent/${agentId}/sessions`) + .method('GET') + .data(params) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail(() => { + RequestService.reAjaxFun(() => { + this.getAgentSessions(agentId, params, callback); + }); + }).send(); + }, + // 获取智能体聊天记录 + getAgentChatHistory(agentId, sessionId, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/agent/${agentId}/chat-history/${sessionId}`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail(() => { + RequestService.reAjaxFun(() => { + this.getAgentChatHistory(agentId, sessionId, callback); + }); + }).send(); + }, + // 获取音频下载ID + getAudioId(audioId, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/agent/audio/${audioId}`) + .method('POST') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail(() => { + RequestService.reAjaxFun(() => { + this.getAudioId(audioId, callback); + }); + }).send(); + }, } diff --git a/main/manager-web/src/apis/module/device.js b/main/manager-web/src/apis/module/device.js index a62db3b0..2f20425d 100644 --- a/main/manager-web/src/apis/module/device.js +++ b/main/manager-web/src/apis/module/device.js @@ -11,7 +11,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('获取设备列表失败:', err); RequestService.reAjaxFun(() => { this.getAgentBindDevices(agentId, callback); @@ -28,7 +28,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('解绑设备失败:', err); RequestService.reAjaxFun(() => { this.unbindDevice(device_id, callback); @@ -44,11 +44,27 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('绑定设备失败:', err); RequestService.reAjaxFun(() => { this.bindDevice(agentId, deviceCode, callback); }); }).send(); }, + enableOtaUpgrade(id, status, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/device/enableOta/${id}/${status}`) + .method('PUT') + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .networkFail((err) => { + console.error('更新OTA状态失败:', err) + this.$message.error(err.msg || '更新OTA状态失败') + RequestService.reAjaxFun(() => { + this.enableOtaUpgrade(id, status, callback) + }) + }).send() + }, } \ No newline at end of file diff --git a/main/manager-web/src/apis/module/dict.js b/main/manager-web/src/apis/module/dict.js new file mode 100644 index 00000000..8919c46b --- /dev/null +++ b/main/manager-web/src/apis/module/dict.js @@ -0,0 +1,227 @@ +import { getServiceUrl } from '../api'; +import RequestService from '../httpRequest'; + +export default { + // 获取字典类型列表 + getDictTypeList(params, callback) { + const queryParams = new URLSearchParams({ + dictType: params.dictType || '', + dictName: params.dictName || '', + page: params.page || 1, + limit: params.limit || 10 + }).toString(); + + RequestService.sendRequest() + .url(`${getServiceUrl()}/admin/dict/type/page?${queryParams}`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .networkFail((err) => { + console.error('获取字典类型列表失败:', err) + this.$message.error(err.msg || '获取字典类型列表失败') + RequestService.reAjaxFun(() => { + this.getDictTypeList(params, callback) + }) + }).send() + }, + + // 获取字典类型详情 + getDictTypeDetail(id, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/admin/dict/type/${id}`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .networkFail((err) => { + console.error('获取字典类型详情失败:', err) + this.$message.error(err.msg || '获取字典类型详情失败') + RequestService.reAjaxFun(() => { + this.getDictTypeDetail(id, callback) + }) + }).send() + }, + + // 新增字典类型 + addDictType(data, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/admin/dict/type/save`) + .method('POST') + .data(data) + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .networkFail((err) => { + console.error('新增字典类型失败:', err) + this.$message.error(err.msg || '新增字典类型失败') + RequestService.reAjaxFun(() => { + this.addDictType(data, callback) + }) + }).send() + }, + + // 更新字典类型 + updateDictType(data, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/admin/dict/type/update`) + .method('PUT') + .data(data) + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .networkFail((err) => { + console.error('更新字典类型失败:', err) + this.$message.error(err.msg || '更新字典类型失败') + RequestService.reAjaxFun(() => { + this.updateDictType(data, callback) + }) + }).send() + }, + + // 删除字典类型 + deleteDictType(ids, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/admin/dict/type/delete`) + .method('POST') + .data(ids) + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .networkFail((err) => { + console.error('删除字典类型失败:', err) + this.$message.error(err.msg || '删除字典类型失败') + RequestService.reAjaxFun(() => { + this.deleteDictType(ids, callback) + }) + }).send() + }, + + // 获取字典数据列表 + getDictDataList(params, callback) { + const queryParams = new URLSearchParams({ + dictTypeId: params.dictTypeId, + dictLabel: params.dictLabel || '', + dictValue: params.dictValue || '', + page: params.page || 1, + limit: params.limit || 10 + }).toString(); + + RequestService.sendRequest() + .url(`${getServiceUrl()}/admin/dict/data/page?${queryParams}`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .networkFail((err) => { + console.error('获取字典数据列表失败:', err) + this.$message.error(err.msg || '获取字典数据列表失败') + RequestService.reAjaxFun(() => { + this.getDictDataList(params, callback) + }) + }).send() + }, + + // 获取字典数据详情 + getDictDataDetail(id, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/admin/dict/data/${id}`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .networkFail((err) => { + console.error('获取字典数据详情失败:', err) + this.$message.error(err.msg || '获取字典数据详情失败') + RequestService.reAjaxFun(() => { + this.getDictDataDetail(id, callback) + }) + }).send() + }, + + // 新增字典数据 + addDictData(data, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/admin/dict/data/save`) + .method('POST') + .data(data) + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .networkFail((err) => { + console.error('新增字典数据失败:', err) + this.$message.error(err.msg || '新增字典数据失败') + RequestService.reAjaxFun(() => { + this.addDictData(data, callback) + }) + }).send() + }, + + // 更新字典数据 + updateDictData(data, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/admin/dict/data/update`) + .method('PUT') + .data(data) + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .networkFail((err) => { + console.error('更新字典数据失败:', err) + this.$message.error(err.msg || '更新字典数据失败') + RequestService.reAjaxFun(() => { + this.updateDictData(data, callback) + }) + }).send() + }, + + // 删除字典数据 + deleteDictData(ids, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/admin/dict/data/delete`) + .method('POST') + .data(ids) + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .networkFail((err) => { + console.error('删除字典数据失败:', err) + this.$message.error(err.msg || '删除字典数据失败') + RequestService.reAjaxFun(() => { + this.deleteDictData(ids, callback) + }) + }).send() + }, + + // 获取字典数据列表 + getDictDataByType(dictType) { + return new Promise((resolve, reject) => { + RequestService.sendRequest() + .url(`${getServiceUrl()}/admin/dict/data/type/${dictType}`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime() + if (res.data && res.data.code === 0) { + resolve(res.data) + } else { + reject(new Error(res.data?.msg || '获取字典数据列表失败')) + } + }) + .networkFail((err) => { + console.error('获取字典数据列表失败:', err) + reject(err) + }).send() + }) + } + +} \ No newline at end of file diff --git a/main/manager-web/src/apis/module/model.js b/main/manager-web/src/apis/module/model.js index 38cc946e..badf4174 100644 --- a/main/manager-web/src/apis/module/model.js +++ b/main/manager-web/src/apis/module/model.js @@ -1,7 +1,6 @@ import { getServiceUrl } from '../api'; import RequestService from '../httpRequest'; - export default { // 获取模型配置列表 getModelList(params, callback) { @@ -19,7 +18,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('获取模型列表失败:', err) RequestService.reAjaxFun(() => { this.getModelList(params, callback) @@ -35,7 +34,7 @@ export default { RequestService.clearRequestTime() callback(res.data?.data || []) }) - .fail((err) => { + .networkFail((err) => { console.error('获取供应器列表失败:', err) this.$message.error('获取供应器列表失败') RequestService.reAjaxFun(() => { @@ -66,7 +65,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('新增模型失败:', err) this.$message.error(err.msg || '新增模型失败') RequestService.reAjaxFun(() => { @@ -83,7 +82,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('删除模型失败:', err) this.$message.error(err.msg || '删除模型失败') RequestService.reAjaxFun(() => { @@ -101,7 +100,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .networkFail(() => { RequestService.reAjaxFun(() => { this.getModelNames(modelType, modelName, callback); }); @@ -119,7 +118,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .networkFail(() => { RequestService.reAjaxFun(() => { this.getModelVoices(modelId, voiceName, callback); }); @@ -134,7 +133,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('获取模型配置失败:', err) this.$message.error(err.msg || '获取模型配置失败') RequestService.reAjaxFun(() => { @@ -142,40 +141,168 @@ export default { }) }).send() }, - // 启用/禁用模型状态 - updateModelStatus(id, status, callback) { - RequestService.sendRequest() - .url(`${getServiceUrl()}/models/enable/${id}/${status}`) - .method('PUT') - .success((res) => { - RequestService.clearRequestTime() - callback(res) + // 启用/禁用模型状态 + updateModelStatus(id, status, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/models/enable/${id}/${status}`) + .method('PUT') + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .networkFail((err) => { + console.error('更新模型状态失败:', err) + this.$message.error(err.msg || '更新模型状态失败') + RequestService.reAjaxFun(() => { + this.updateModelStatus(id, status, callback) }) - .fail((err) => { - console.error('更新模型状态失败:', err) - this.$message.error(err.msg || '更新模型状态失败') - RequestService.reAjaxFun(() => { - this.updateModelStatus(id, status, callback) - }) - }).send() - }, - // 更新模型配置 - updateModel(params, callback) { - const { modelType, provideCode, id, formData } = params; - RequestService.sendRequest() - .url(`${getServiceUrl()}/models/${modelType}/${provideCode}/${id}`) - .method('PUT') - .data(formData) - .success((res) => { - RequestService.clearRequestTime(); - callback(res); + }).send() + }, + // 更新模型配置 + updateModel(params, callback) { + const { modelType, provideCode, id, formData } = params; + const payload = { + ...formData, + configJson: formData.configJson + }; + RequestService.sendRequest() + .url(`${getServiceUrl()}/models/${modelType}/${provideCode}/${id}`) + .method('PUT') + .data(payload) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail((err) => { + console.error('更新模型失败:', err); + this.$message.error(err.msg || '更新模型失败'); + RequestService.reAjaxFun(() => { + this.updateModel(params, callback); + }); + }).send(); + }, + // 设置默认模型 + setDefaultModel(id, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/models/default/${id}`) + .method('PUT') + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .networkFail((err) => { + console.error('设置默认模型失败:', err) + this.$message.error(err.msg || '设置默认模型失败') + RequestService.reAjaxFun(() => { + this.setDefaultModel(id, callback) }) - .fail((err) => { - console.error('更新模型失败:', err); - this.$message.error(err.msg || '更新模型失败'); - RequestService.reAjaxFun(() => { - this.updateModel(params, callback); - }); - }).send(); - }, + }).send() + }, + + /** + * 获取模型配置列表(支持查询参数) + * @param {Object} params - 查询参数对象,例如 { name: 'test', modelType: 1 } + * @param {Function} callback - 回调函数 + */ + getModelProvidersPage(params, callback) { + // 构建查询参数 + const queryParams = new URLSearchParams(); + if (params.name) queryParams.append('name', params.name); + if (params.modelType !== undefined) queryParams.append('modelType', params.modelType); + if (params.page !== undefined) queryParams.append('page', params.page); + if (params.limit !== undefined) queryParams.append('limit', params.limit); + + RequestService.sendRequest() + .url(`${getServiceUrl()}/models/provider?${queryParams.toString()}`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail((err) => { + this.$message.error(err.msg || '获取供应器列表失败'); + RequestService.reAjaxFun(() => { + this.getModelProviders(params, callback); + }); + }).send(); + }, + + /** + * 新增模型供应器配置 + * @param {Object} params - 请求参数对象,例如 { modelType: '1', providerCode: '1', name: '1', fields: '1', sort: 1 } + * @param {Function} callback - 成功回调函数 + */ + addModelProvider(params, callback) { + const postData = { + modelType: params.modelType || '', + providerCode: params.providerCode || '', + name: params.name || '', + fields: JSON.stringify(params.fields || []), + sort: params.sort || 0 + }; + + RequestService.sendRequest() + .url(`${getServiceUrl()}/models/provider`) + .method('POST') + .data(postData) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail((err) => { + console.error('新增模型供应器失败:', err) + this.$message.error(err.msg || '新增模型供应器失败') + RequestService.reAjaxFun(() => { + this.addModelProvider(params, callback); + }); + }).send(); + }, + + /** + * 更新模型供应器配置 + * @param {Object} params - 请求参数对象,例如 { id: '111', modelType: '1', providerCode: '1', name: '1', fields: '1', sort: 1 } + * @param {Function} callback - 成功回调函数 + */ + updateModelProvider(params, callback) { + const putData = { + id: params.id || '', + modelType: params.modelType || '', + providerCode: params.providerCode || '', + name: params.name || '', + fields: JSON.stringify(params.fields || []), + sort: params.sort || 0 + }; + + RequestService.sendRequest() + .url(`${getServiceUrl()}/models/provider`) + .method('PUT') + .data(putData) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail((err) => { + this.$message.error(err.msg || '更新模型供应器失败') + RequestService.reAjaxFun(() => { + this.updateModelProvider(params, callback); + }); + }).send(); + }, + // 删除 + deleteModelProviderByIds(ids, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/models/provider/delete`) + .method('POST') + .data(ids) + .success((res) => { + RequestService.clearRequestTime() + callback(res); + }) + .networkFail((err) => { + this.$message.error(err.msg || '删除模型供应器失败') + RequestService.reAjaxFun(() => { + this.deleteModelProviderByIds(ids, callback) + }) + }).send() + }, } diff --git a/main/manager-web/src/apis/module/ota.js b/main/manager-web/src/apis/module/ota.js new file mode 100644 index 00000000..4db86ad7 --- /dev/null +++ b/main/manager-web/src/apis/module/ota.js @@ -0,0 +1,123 @@ +import { getServiceUrl } from '../api'; +import RequestService from '../httpRequest'; + +export default { + // 分页查询OTA固件信息 + getOtaList(params, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/otaMag`) + .method('GET') + .data(params) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail((err) => { + console.error('获取OTA固件列表失败:', err); + RequestService.reAjaxFun(() => { + this.getOtaList(params, callback); + }); + }).send(); + }, + // 获取单个OTA固件信息 + getOtaInfo(id, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/otaMag/${id}`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail((err) => { + console.error('获取OTA固件信息失败:', err); + RequestService.reAjaxFun(() => { + this.getOtaInfo(id, callback); + }); + }).send(); + }, + // 保存OTA固件信息 + saveOta(entity, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/otaMag`) + .method('POST') + .data(entity) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail((err) => { + console.error('保存OTA固件信息失败:', err); + RequestService.reAjaxFun(() => { + this.saveOta(entity, callback); + }); + }).send(); + }, + // 更新OTA固件信息 + updateOta(id, entity, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/otaMag/${id}`) + .method('PUT') + .data(entity) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail((err) => { + console.error('更新OTA固件信息失败:', err); + RequestService.reAjaxFun(() => { + this.updateOta(id, entity, callback); + }); + }).send(); + }, + // 删除OTA固件 + deleteOta(id, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/otaMag/${id}`) + .method('DELETE') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail((err) => { + console.error('删除OTA固件失败:', err); + RequestService.reAjaxFun(() => { + this.deleteOta(id, callback); + }); + }).send(); + }, + // 上传固件文件 + uploadFirmware(file, callback) { + const formData = new FormData(); + formData.append('file', file); + RequestService.sendRequest() + .url(`${getServiceUrl()}/otaMag/upload`) + .method('POST') + .data(formData) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail((err) => { + console.error('上传固件文件失败:', err); + RequestService.reAjaxFun(() => { + this.uploadFirmware(file, callback); + }); + }).send(); + }, + // 获取固件下载链接 + getDownloadUrl(id, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/otaMag/getDownloadUrl/${id}`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail((err) => { + console.error('获取下载链接失败:', err); + RequestService.reAjaxFun(() => { + this.getDownloadUrl(id, callback); + }); + }).send(); + } +} \ No newline at end of file diff --git a/main/manager-web/src/apis/module/timbre.js b/main/manager-web/src/apis/module/timbre.js index 9b48e5c3..7aa6a711 100644 --- a/main/manager-web/src/apis/module/timbre.js +++ b/main/manager-web/src/apis/module/timbre.js @@ -1,11 +1,8 @@ -// timbre.js -import {getServiceUrl} from '../api'; +import { getServiceUrl } from '../api'; import RequestService from '../httpRequest'; export default { - /** - * 获取音色数据 - */ + // 获取音色 getVoiceList(params, callback) { const queryParams = new URLSearchParams({ ttsModelId: params.ttsModelId, @@ -21,16 +18,14 @@ export default { RequestService.clearRequestTime(); callback(res.data || []); }) - .fail((err) => { + .networkFail((err) => { console.error('获取音色列表失败:', err); RequestService.reAjaxFun(() => { this.getVoiceList(params, callback); }); }).send(); }, - /** - * 音色保存 - */ + // 音色保存 saveVoice(params, callback) { RequestService.sendRequest() .url(`${getServiceUrl()}/ttsVoice`) @@ -47,36 +42,31 @@ export default { .success((res) => { callback(res.data); }) - .fail((err) => { + .networkFail((err) => { console.error('保存音色失败:', err); RequestService.reAjaxFun(() => { this.saveVoice(params, callback); }); }).send(); }, - /** - * 音色删除 - */ - deleteVoice(id, callback) { - console.log('尝试删除音色,ID:', id); + // 音色删除 + deleteVoice(ids, callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/ttsVoice`) - .method('DELETE') - .data({ ids: [id] }) + .url(`${getServiceUrl()}/ttsVoice/delete`) + .method('POST') + .data(ids) .success((res) => { RequestService.clearRequestTime() callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('删除音色失败:', err); RequestService.reAjaxFun(() => { - this.deleteVoice(id, callback); + this.deleteVoice(ids, callback); }); }).send(); }, - /** - * 音色修改 - */ + // 音色修改 updateVoice(params, callback) { RequestService.sendRequest() .url(`${getServiceUrl()}/ttsVoice/${params.id}`) @@ -92,7 +82,7 @@ export default { .success((res) => { callback(res.data); }) - .fail((err) => { + .networkFail((err) => { console.error('修改音色失败:', err); RequestService.reAjaxFun(() => { this.updateVoice(params, callback); diff --git a/main/manager-web/src/apis/module/user.js b/main/manager-web/src/apis/module/user.js index 71920458..6c16c3e6 100755 --- a/main/manager-web/src/apis/module/user.js +++ b/main/manager-web/src/apis/module/user.js @@ -4,7 +4,7 @@ import RequestService from '../httpRequest' export default { // 登录 - login(loginForm, callback) { + login(loginForm, callback, failCallback) { RequestService.sendRequest() .url(`${getServiceUrl()}/user/login`) .method('POST') @@ -13,7 +13,11 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail(() => { + .fail((err) => { + RequestService.clearRequestTime() + failCallback(err) + }) + .networkFail(() => { RequestService.reAjaxFun(() => { this.login(loginForm, callback) }) @@ -34,12 +38,32 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { // 添加错误参数 + .networkFail((err) => { // 添加错误参数 }).send() }, + // 发送短信验证码 + sendSmsVerification(data, callback, failCallback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/user/smsVerification`) + .method('POST') + .data(data) + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .fail((err) => { + RequestService.clearRequestTime() + failCallback(err) + }) + .networkFail(() => { + RequestService.reAjaxFun(() => { + this.sendSmsVerification(data, callback, failCallback) + }) + }).send() + }, // 注册账号 - register(registerForm, callback) { + register(registerForm, callback, failCallback) { RequestService.sendRequest() .url(`${getServiceUrl()}/user/register`) .method('POST') @@ -48,7 +72,14 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail(() => { + .fail((err) => { + RequestService.clearRequestTime() + failCallback(err) + }) + .networkFail(() => { + RequestService.reAjaxFun(() => { + this.register(registerForm, callback, failCallback) + }) }).send() }, // 保存设备配置 @@ -61,7 +92,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('保存配置失败:', err); RequestService.reAjaxFun(() => { this.saveDeviceConfig(device_id, configData, callback); @@ -77,7 +108,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('接口请求失败:', err) RequestService.reAjaxFun(() => { this.getUserInfo(callback) @@ -97,7 +128,7 @@ export default { RequestService.clearRequestTime(); successCallback(res); }) - .fail((error) => { + .networkFail((error) => { RequestService.reAjaxFun(() => { this.changePassword(oldPassword, newPassword, successCallback, errorCallback); }); @@ -106,7 +137,8 @@ export default { }, // 修改用户状态 changeUserStatus(status, userIds, successCallback) { - return RequestService.sendRequest() + console.log(555, userIds) + RequestService.sendRequest() .url(`${getServiceUrl()}/admin/users/changeStatus/${status}`) .method('put') .data(userIds) @@ -114,11 +146,51 @@ export default { RequestService.clearRequestTime() successCallback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('修改用户状态失败:', err) RequestService.reAjaxFun(() => { this.changeUserStatus(status, userIds) }) }).send() }, + // 获取公共配置 + getPubConfig(callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/user/pub-config`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail((err) => { + console.error('获取公共配置失败:', err); + RequestService.reAjaxFun(() => { + this.getPubConfig(callback); + }); + }).send(); + }, + // 找回用户密码 + retrievePassword(passwordData, callback, failCallback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/user/retrieve-password`) + .method('PUT') + .data({ + phone: passwordData.phone, + code: passwordData.code, + password: passwordData.password + }) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + RequestService.clearRequestTime(); + failCallback(err); + }) + .networkFail(() => { + RequestService.reAjaxFun(() => { + this.retrievePassword(passwordData, callback, failCallback); + }); + }).send() + } } diff --git a/main/manager-web/src/assets/header/firmware_update.png b/main/manager-web/src/assets/header/firmware_update.png new file mode 100644 index 00000000..bd3d7396 Binary files /dev/null and b/main/manager-web/src/assets/header/firmware_update.png differ diff --git a/main/manager-web/src/assets/login/login-person.png b/main/manager-web/src/assets/login/login-person.png index 27477f2f..081eff3e 100644 Binary files a/main/manager-web/src/assets/login/login-person.png and b/main/manager-web/src/assets/login/login-person.png differ diff --git a/main/manager-web/src/assets/model/model.png b/main/manager-web/src/assets/model/model.png index f08ecb86..21132b76 100644 Binary files a/main/manager-web/src/assets/model/model.png and b/main/manager-web/src/assets/model/model.png differ diff --git a/main/manager-web/src/assets/user-avatar1.png b/main/manager-web/src/assets/user-avatar1.png new file mode 100644 index 00000000..d835d2f7 Binary files /dev/null and b/main/manager-web/src/assets/user-avatar1.png differ diff --git a/main/manager-web/src/assets/user-avatar2.png b/main/manager-web/src/assets/user-avatar2.png new file mode 100644 index 00000000..71d5fd1b Binary files /dev/null and b/main/manager-web/src/assets/user-avatar2.png differ diff --git a/main/manager-web/src/assets/user-avatar3.png b/main/manager-web/src/assets/user-avatar3.png new file mode 100644 index 00000000..159e6886 Binary files /dev/null and b/main/manager-web/src/assets/user-avatar3.png differ diff --git a/main/manager-web/src/assets/user-avatar4.png b/main/manager-web/src/assets/user-avatar4.png new file mode 100644 index 00000000..0d078144 Binary files /dev/null and b/main/manager-web/src/assets/user-avatar4.png differ diff --git a/main/manager-web/src/assets/user-avatar5.png b/main/manager-web/src/assets/user-avatar5.png new file mode 100644 index 00000000..c9960475 Binary files /dev/null and b/main/manager-web/src/assets/user-avatar5.png differ diff --git a/main/manager-web/src/components/AddDeviceDialog.vue b/main/manager-web/src/components/AddDeviceDialog.vue index bc7da372..b273a8dc 100644 --- a/main/manager-web/src/components/AddDeviceDialog.vue +++ b/main/manager-web/src/components/AddDeviceDialog.vue @@ -1,5 +1,5 @@