diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 12f9b83e..2cced465 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -8,10 +8,10 @@ body: label: 消息平台适配器 description: "连接QQ使用的框架" options: - - yiri-mirai(Mirai) - Nakuru(go-cqhttp) - aiocqhttp(使用 OneBot 协议接入的) - qq-botpy(QQ官方API) + - yiri-mirai(Mirai) validations: required: false - type: input @@ -23,8 +23,8 @@ body: required: true - type: input attributes: - label: QChatGPT版本 - description: QChatGPT版本号 + label: LangBot 版本 + description: LangBot (QChatGPT) 版本号 placeholder: 例如:v3.3.0,可以使用`!version`命令查看,或者到 pkg/utils/constants.py 查看 validations: required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d1973eb3..53c58493 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,5 +10,4 @@ updates: schedule: interval: "weekly" allow: - - dependency-name: "yiri-mirai-rc" - dependency-name: "openai" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6b636c25..829e3018 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -6,8 +6,11 @@ ### PR 作者完成 -- [ ] 阅读仓库[贡献指引](https://github.com/RockChinQ/QChatGPT/blob/master/CONTRIBUTING.md)了吗? +*请在方括号间写`x`以打勾 + +- [ ] 阅读仓库[贡献指引](https://github.com/RockChinQ/LangBot/blob/master/CONTRIBUTING.md)了吗? - [ ] 与项目所有者沟通过了吗? +- [ ] 我确定已自行测试所作的更改,确保功能符合预期。 ### 项目所有者完成 diff --git a/.github/workflows/build-dev-image.yaml b/.github/workflows/build-dev-image.yaml new file mode 100644 index 00000000..28a46678 --- /dev/null +++ b/.github/workflows/build-dev-image.yaml @@ -0,0 +1,24 @@ +name: Build Dev Image + +on: + push: + workflow_dispatch: + +jobs: + build-dev-image: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Generate Tag + id: generate_tag + run: | + # 获取分支名称,把/替换为- + echo ${{ github.ref }} | sed 's/refs\/heads\///g' | sed 's/\//-/g' + echo ::set-output name=tag::$(echo ${{ github.ref }} | sed 's/refs\/heads\///g' | sed 's/\//-/g') + - name: Login to Registry + run: docker login --username=${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }} + - name: Build Docker Image + run: | + docker buildx create --name mybuilder --use + docker build -t rockchin/langbot:${{ steps.generate_tag.outputs.tag }} . --push diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index c877667a..63cbf3d5 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -19,12 +19,6 @@ jobs: export GITHUB_REF=${{ github.ref }} echo $GITHUB_REF fi - # - name: Check GITHUB_REF env - # run: echo $GITHUB_REF - # - name: Get version # 在 GitHub Actions 运行环境 - # id: get_version - # if: (startsWith(env.GITHUB_REF, 'refs/tags/')||startsWith(github.ref, 'refs/tags/')) && startsWith(github.repository, 'RockChinQ/QChatGPT') - # run: export GITHUB_REF=${GITHUB_REF/refs\/tags\//} - name: Check version id: check_version run: | @@ -44,5 +38,5 @@ jobs: run: docker login --username=${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }} - name: Create Buildx run: docker buildx create --name mybuilder --use - - name: Build # image name: rockchin/qchatgpt: - run: docker buildx build --platform linux/arm64,linux/amd64 -t rockchin/qchatgpt:${{ steps.check_version.outputs.version }} -t rockchin/qchatgpt:latest . --push + - name: Build # image name: rockchin/langbot: + run: docker buildx build --platform linux/arm64,linux/amd64 -t rockchin/langbot:${{ steps.check_version.outputs.version }} -t rockchin/langbot:latest . --push diff --git a/.github/workflows/build-release-artifacts.yaml b/.github/workflows/build-release-artifacts.yaml new file mode 100644 index 00000000..89ff9cae --- /dev/null +++ b/.github/workflows/build-release-artifacts.yaml @@ -0,0 +1,52 @@ +name: Build Release Artifacts + +on: + workflow_dispatch: + ## 发布release的时候会自动构建 + release: + types: [published] + +jobs: + build-artifacts: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Check version + id: check_version + run: | + echo $GITHUB_REF + # 如果是tag,则去掉refs/tags/前缀 + if [[ $GITHUB_REF == refs/tags/* ]]; then + echo "It's a tag" + echo $GITHUB_REF + echo $GITHUB_REF | awk -F '/' '{print $3}' + echo ::set-output name=version::$(echo $GITHUB_REF | awk -F '/' '{print $3}') + else + echo "It's not a tag" + echo $GITHUB_REF + echo ::set-output name=version::${GITHUB_REF} + fi + + - name: Make Temp Directory + run: | + mkdir -p /tmp/langbot_build_web + cp -r . /tmp/langbot_build_web + - name: Setup Node + uses: actions/setup-node@v2 + with: + node-version: '22' + - name: Build Web + run: | + cd /tmp/langbot_build_web/web + npm install + npm run build + - name: Package Output + run: | + cp -r /tmp/langbot_build_web/web/dist ./web + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: langbot-${{ steps.check_version.outputs.version }}-all + path: . diff --git a/.github/workflows/sync-wiki.yml b/.github/workflows/sync-wiki.yml deleted file mode 100644 index 0d943c21..00000000 --- a/.github/workflows/sync-wiki.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Update Wiki - -on: - push: - branches: - - master - paths: - - 'res/wiki/**' - -jobs: - update-wiki: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup Git - run: | - git config --global user.name "GitHub Actions" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - - name: Clone Wiki Repository - uses: actions/checkout@v2 - with: - repository: RockChinQ/QChatGPT.wiki - path: wiki - - name: Delete old wiki content - run: | - rm -rf wiki/* - - name: Copy res/wiki content to wiki - run: | - cp -r res/wiki/* wiki/ - - name: Check for changes - run: | - cd wiki - if git diff --quiet; then - echo "No changes to commit." - exit 0 - fi - - name: Commit and Push Changes - run: | - cd wiki - git add . - git commit -m "Update wiki" - git push diff --git a/.gitignore b/.gitignore index 56282ca8..db3cfc7d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,10 @@ __pycache__/ database.db qchatgpt.log +langbot.log /banlist.py -plugins/ -!plugins/__init__.py +/plugins/ +!/plugins/__init__.py /revcfg.py prompts/ logs/ @@ -34,4 +35,5 @@ bard.json res/instance_id.json .DS_Store /data -botpy.log* \ No newline at end of file +botpy.log* +/poc \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 89f09504..428ad14d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,19 @@ +FROM node:22-alpine AS node + +WORKDIR /app + +COPY web ./web + +RUN cd web && npm install && npm run build + FROM python:3.10.13-slim + WORKDIR /app COPY . . +COPY --from=node /app/web/dist ./web/dist + RUN apt update \ && apt install gcc -y \ && python -m pip install -r requirements.txt \ diff --git a/README.md b/README.md index a4e5b9a4..69b342c7 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@

-LangBot +QChatGPT

-# QChatGPT +# LangBot RockChinQ%2FQChatGPT | Trendshift -[![GitHub release (latest by date)](https://img.shields.io/github/v/release/RockChinQ/QChatGPT)](https://github.com/RockChinQ/QChatGPT/releases/latest) - - docker pull +[![GitHub release (latest by date)](https://img.shields.io/github/v/release/RockChinQ/LangBot)](https://github.com/RockChinQ/LangBot/releases/latest) + + docker pull - ![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.qchatgpt.rockchin.top%2Fapi%2Fv2%2Fview%2Frealtime%2Fcount_query%3Fminute%3D10080&query=%24.data.count&label=%E4%BD%BF%E7%94%A8%E9%87%8F%EF%BC%887%E6%97%A5%EF%BC%89) + ![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.docs.langbot.app%2Fapi%2Fv2%2Fview%2Frealtime%2Fcount_query%3Fminute%3D10080&query=%24.data.count&label=%E4%BD%BF%E7%94%A8%E9%87%8F%EF%BC%887%E6%97%A5%EF%BC%89) ![Wakapi Count](https://wakapi.rockchin.top/api/badge/RockChinQ/interval:any/project:QChatGPT)
python @@ -28,12 +28,12 @@ ## 使用文档 -项目主页 | -功能介绍 | -部署文档 | -常见问题 | -插件介绍 | -提交插件 +项目主页 | +功能介绍 | +部署文档 | +常见问题 | +插件介绍 | +提交插件 ## 相关链接 @@ -42,14 +42,5 @@ 遥测服务端源码官方文档储存库 - - - -回复效果(带有联网插件) +回复效果(带有联网插件)
diff --git a/docker-compose.yaml b/docker-compose.yaml index bd6067cd..e78d66e8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,10 +1,13 @@ version: "3" services: - qchatgpt: - image: rockchin/qchatgpt:latest + langbot: + image: rockchin/langbot:latest volumes: - ./data:/app/data - ./plugins:/app/plugins restart: on-failure - # 根据具体环境配置网络 \ No newline at end of file + ports: + - 5300:5300 # 供 WebUI 使用 + - 2280-2290:2280-2290 # 供消息平台适配器方向连接 + # 根据具体环境配置网络 diff --git a/main.py b/main.py index 0ad87bd2..9254f3f5 100644 --- a/main.py +++ b/main.py @@ -1,19 +1,23 @@ -# QChatGPT 终端启动入口 +# LangBot 终端启动入口 # 在此层级解决依赖项检查。 -# QChatGPT/main.py +# LangBot/main.py asciiart = r""" - ___ ___ _ _ ___ ___ _____ - / _ \ / __| |_ __ _| |_ / __| _ \_ _| -| (_) | (__| ' \/ _` | _| (_ | _/ | | - \__\_\\___|_||_\__,_|\__|\___|_| |_| - -⭐️开源地址: https://github.com/RockChinQ/QChatGPT -📖文档地址: https://q.rkcn.top + _ ___ _ +| | __ _ _ _ __ _| _ ) ___| |_ +| |__/ _` | ' \/ _` | _ \/ _ \ _| +|____\__,_|_||_\__, |___/\___/\__| + |___/ + +⭐️开源地址: https://github.com/RockChinQ/LangBot +📖文档地址: https://docs.langbot.app """ -async def main_entry(): +import asyncio + + +async def main_entry(loop: asyncio.AbstractEventLoop): print(asciiart) import sys @@ -46,13 +50,20 @@ async def main_entry(): sys.exit(0) from pkg.core import boot - await boot.main() + await boot.main(loop) if __name__ == '__main__': import os + import sys + + # 必须大于 3.10.1 + if sys.version_info < (3, 10, 1): + print("需要 Python 3.10.1 及以上版本,当前 Python 版本为:", sys.version) + input("按任意键退出...") + exit(1) - # 检查本目录是否有main.py,且包含QChatGPT字符串 + # 检查本目录是否有main.py,且包含LangBot字符串 invalid_pwd = False if not os.path.exists('main.py'): @@ -60,13 +71,13 @@ async def main_entry(): else: with open('main.py', 'r', encoding='utf-8') as f: content = f.read() - if "QChatGPT/main.py" not in content: + if "LangBot/main.py" not in content: invalid_pwd = True if invalid_pwd: - print("请在QChatGPT项目根目录下以命令形式运行此程序。") + print("请在 LangBot 项目根目录下以命令形式运行此程序。") input("按任意键退出...") - exit(0) + exit(1) - import asyncio + loop = asyncio.new_event_loop() - asyncio.run(main_entry()) + loop.run_until_complete(main_entry(loop)) diff --git a/pkg/core/bootutils/misc.py b/pkg/api/__init__.py similarity index 100% rename from pkg/core/bootutils/misc.py rename to pkg/api/__init__.py diff --git a/pkg/api/http/__init__.py b/pkg/api/http/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pkg/api/http/controller/__init__.py b/pkg/api/http/controller/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pkg/api/http/controller/group.py b/pkg/api/http/controller/group.py new file mode 100644 index 00000000..5a6ab97e --- /dev/null +++ b/pkg/api/http/controller/group.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +import abc +import typing +import enum +import quart +from quart.typing import RouteCallable + +from ....core import app + + +preregistered_groups: list[type[RouterGroup]] = [] +"""RouterGroup 的预注册列表""" + +def group_class(name: str, path: str) -> None: + """注册一个 RouterGroup""" + + def decorator(cls: typing.Type[RouterGroup]) -> typing.Type[RouterGroup]: + cls.name = name + cls.path = path + preregistered_groups.append(cls) + return cls + + return decorator + + +class AuthType(enum.Enum): + """认证类型""" + NONE = 'none' + USER_TOKEN = 'user-token' + + +class RouterGroup(abc.ABC): + + name: str + + path: str + + ap: app.Application + + quart_app: quart.Quart + + def __init__(self, ap: app.Application, quart_app: quart.Quart) -> None: + self.ap = ap + self.quart_app = quart_app + + @abc.abstractmethod + async def initialize(self) -> None: + pass + + def route(self, rule: str, auth_type: AuthType = AuthType.USER_TOKEN, **options: typing.Any) -> typing.Callable[[RouteCallable], RouteCallable]: # decorator + """注册一个路由""" + def decorator(f: RouteCallable) -> RouteCallable: + nonlocal rule + rule = self.path + rule + + async def handler_error(*args, **kwargs): + + if auth_type == AuthType.USER_TOKEN: + # 从Authorization头中获取token + token = quart.request.headers.get('Authorization', '').replace('Bearer ', '') + + if not token: + return self.http_status(401, -1, '未提供有效的用户令牌') + + try: + user_email = await self.ap.user_service.verify_jwt_token(token) + + # 检查f是否接受user_email参数 + if 'user_email' in f.__code__.co_varnames: + kwargs['user_email'] = user_email + except Exception as e: + return self.http_status(401, -1, str(e)) + + try: + return await f(*args, **kwargs) + except Exception as e: # 自动 500 + return self.http_status(500, -2, str(e)) + + new_f = handler_error + new_f.__name__ = (self.name + rule).replace('/', '__') + new_f.__doc__ = f.__doc__ + + self.quart_app.route(rule, **options)(new_f) + return f + + return decorator + + def success(self, data: typing.Any = None) -> quart.Response: + """返回一个 200 响应""" + return quart.jsonify({ + 'code': 0, + 'msg': 'ok', + 'data': data, + }) + + def fail(self, code: int, msg: str) -> quart.Response: + """返回一个异常响应""" + + return quart.jsonify({ + 'code': code, + 'msg': msg, + }) + + def http_status(self, status: int, code: int, msg: str) -> quart.Response: + """返回一个指定状态码的响应""" + return self.fail(code, msg), status diff --git a/pkg/api/http/controller/groups/__init__.py b/pkg/api/http/controller/groups/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pkg/api/http/controller/groups/logs.py b/pkg/api/http/controller/groups/logs.py new file mode 100644 index 00000000..36aa0d75 --- /dev/null +++ b/pkg/api/http/controller/groups/logs.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +import traceback + +import quart + +from .....core import app +from .. import group + + +@group.group_class('logs', '/api/v1/logs') +class LogsRouterGroup(group.RouterGroup): + + async def initialize(self) -> None: + @self.route('', methods=['GET']) + async def _() -> str: + + start_page_number = int(quart.request.args.get('start_page_number', 0)) + start_offset = int(quart.request.args.get('start_offset', 0)) + + logs_str, end_page_number, end_offset = self.ap.log_cache.get_log_by_pointer( + start_page_number=start_page_number, + start_offset=start_offset + ) + + return self.success( + data={ + "logs": logs_str, + "end_page_number": end_page_number, + "end_offset": end_offset + } + ) diff --git a/pkg/api/http/controller/groups/plugins.py b/pkg/api/http/controller/groups/plugins.py new file mode 100644 index 00000000..86ac7ec6 --- /dev/null +++ b/pkg/api/http/controller/groups/plugins.py @@ -0,0 +1,84 @@ +from __future__ import annotations + + +import traceback + +import quart + +from .....core import app, taskmgr +from .. import group + + +@group.group_class('plugins', '/api/v1/plugins') +class PluginsRouterGroup(group.RouterGroup): + + async def initialize(self) -> None: + @self.route('', methods=['GET']) + async def _() -> str: + plugins = self.ap.plugin_mgr.plugins() + + plugins_data = [plugin.model_dump() for plugin in plugins] + + return self.success(data={ + 'plugins': plugins_data + }) + + @self.route('///toggle', methods=['PUT']) + async def _(author: str, plugin_name: str) -> str: + data = await quart.request.json + target_enabled = data.get('target_enabled') + await self.ap.plugin_mgr.update_plugin_switch(plugin_name, target_enabled) + return self.success() + + @self.route('///update', methods=['POST']) + async def _(author: str, plugin_name: str) -> str: + ctx = taskmgr.TaskContext.new() + wrapper = self.ap.task_mgr.create_user_task( + self.ap.plugin_mgr.update_plugin(plugin_name, task_context=ctx), + kind="plugin-operation", + name=f"plugin-update-{plugin_name}", + label=f"更新插件 {plugin_name}", + context=ctx + ) + return self.success(data={ + 'task_id': wrapper.id + }) + + @self.route('//', methods=['DELETE']) + async def _(author: str, plugin_name: str) -> str: + ctx = taskmgr.TaskContext.new() + wrapper = self.ap.task_mgr.create_user_task( + self.ap.plugin_mgr.uninstall_plugin(plugin_name, task_context=ctx), + kind="plugin-operation", + name=f'plugin-remove-{plugin_name}', + label=f'删除插件 {plugin_name}', + context=ctx + ) + + return self.success(data={ + 'task_id': wrapper.id + }) + + @self.route('/reorder', methods=['PUT']) + async def _() -> str: + data = await quart.request.json + await self.ap.plugin_mgr.reorder_plugins(data.get('plugins')) + return self.success() + + @self.route('/install/github', methods=['POST']) + async def _() -> str: + data = await quart.request.json + + ctx = taskmgr.TaskContext.new() + short_source_str = data['source'][-8:] + wrapper = self.ap.task_mgr.create_user_task( + self.ap.plugin_mgr.install_plugin(data['source'], task_context=ctx), + kind="plugin-operation", + name=f'plugin-install-github', + label=f'安装插件 ...{short_source_str}', + context=ctx + ) + + return self.success(data={ + 'task_id': wrapper.id + }) diff --git a/pkg/api/http/controller/groups/settings.py b/pkg/api/http/controller/groups/settings.py new file mode 100644 index 00000000..00693239 --- /dev/null +++ b/pkg/api/http/controller/groups/settings.py @@ -0,0 +1,62 @@ +import quart + +from .....core import app +from .. import group + + +@group.group_class('settings', '/api/v1/settings') +class SettingsRouterGroup(group.RouterGroup): + + async def initialize(self) -> None: + + @self.route('', methods=['GET']) + async def _() -> str: + return self.success( + data={ + "managers": [ + { + "name": m.name, + "description": m.description, + } + for m in self.ap.settings_mgr.get_manager_list() + ] + } + ) + + @self.route('/', methods=['GET']) + async def _(manager_name: str) -> str: + + manager = self.ap.settings_mgr.get_manager(manager_name) + + if manager is None: + return self.fail(1, '配置管理器不存在') + + return self.success( + data={ + "manager": { + "name": manager.name, + "description": manager.description, + "schema": manager.schema, + "file": manager.file.config_file_name, + "data": manager.data, + "doc_link": manager.doc_link + } + } + ) + + @self.route('//data', methods=['PUT']) + async def _(manager_name: str) -> str: + data = await quart.request.json + manager = self.ap.settings_mgr.get_manager(manager_name) + + if manager is None: + return self.fail(code=1, msg='配置管理器不存在') + + # manager.data = data['data'] + for k, v in data['data'].items(): + manager.data[k] = v + + await manager.dump_config() + return self.success(data={ + "data": manager.data + }) diff --git a/pkg/api/http/controller/groups/stats.py b/pkg/api/http/controller/groups/stats.py new file mode 100644 index 00000000..45326035 --- /dev/null +++ b/pkg/api/http/controller/groups/stats.py @@ -0,0 +1,23 @@ +import quart +import asyncio + +from .....core import app, taskmgr +from .. import group + + +@group.group_class('stats', '/api/v1/stats') +class StatsRouterGroup(group.RouterGroup): + + async def initialize(self) -> None: + @self.route('/basic', methods=['GET']) + async def _() -> str: + + conv_count = 0 + for session in self.ap.sess_mgr.session_list: + conv_count += len(session.conversations if session.conversations is not None else []) + + return self.success(data={ + 'active_session_count': len(self.ap.sess_mgr.session_list), + 'conversation_count': conv_count, + 'query_count': self.ap.query_pool.query_id_counter, + }) diff --git a/pkg/api/http/controller/groups/system.py b/pkg/api/http/controller/groups/system.py new file mode 100644 index 00000000..f074531a --- /dev/null +++ b/pkg/api/http/controller/groups/system.py @@ -0,0 +1,63 @@ +import quart +import asyncio + +from .....core import app, taskmgr +from .. import group +from .....utils import constants + + +@group.group_class('system', '/api/v1/system') +class SystemRouterGroup(group.RouterGroup): + + async def initialize(self) -> None: + @self.route('/info', methods=['GET'], auth_type=group.AuthType.NONE) + async def _() -> str: + return self.success( + data={ + "version": constants.semantic_version, + "debug": constants.debug_mode, + "enabled_platform_count": len(self.ap.platform_mgr.adapters) + } + ) + + @self.route('/tasks', methods=['GET']) + async def _() -> str: + task_type = quart.request.args.get("type") + + if task_type == '': + task_type = None + + return self.success( + data=self.ap.task_mgr.get_tasks_dict(task_type) + ) + + @self.route('/tasks/', methods=['GET']) + async def _(task_id: str) -> str: + task = self.ap.task_mgr.get_task_by_id(int(task_id)) + + if task is None: + return self.http_status(404, 404, "Task not found") + + return self.success(data=task.to_dict()) + + @self.route('/reload', methods=['POST']) + async def _() -> str: + json_data = await quart.request.json + + scope = json_data.get("scope") + + await self.ap.reload( + scope=scope + ) + return self.success() + + @self.route('/_debug/exec', methods=['POST']) + async def _() -> str: + if not constants.debug_mode: + return self.http_status(403, 403, "Forbidden") + + py_code = await quart.request.data + + ap = self.ap + + return self.success(data=exec(py_code, {"ap": ap})) diff --git a/pkg/api/http/controller/groups/user.py b/pkg/api/http/controller/groups/user.py new file mode 100644 index 00000000..9bc8bf74 --- /dev/null +++ b/pkg/api/http/controller/groups/user.py @@ -0,0 +1,43 @@ +import quart +import sqlalchemy + +from .. import group +from .....persistence.entities import user + + +@group.group_class('user', '/api/v1/user') +class UserRouterGroup(group.RouterGroup): + + async def initialize(self) -> None: + @self.route('/init', methods=['GET', 'POST'], auth_type=group.AuthType.NONE) + async def _() -> str: + if quart.request.method == 'GET': + return self.success(data={ + 'initialized': await self.ap.user_service.is_initialized() + }) + + if await self.ap.user_service.is_initialized(): + return self.fail(1, '系统已初始化') + + json_data = await quart.request.json + + user_email = json_data['user'] + password = json_data['password'] + + await self.ap.user_service.create_user(user_email, password) + + return self.success() + + @self.route('/auth', methods=['POST'], auth_type=group.AuthType.NONE) + async def _() -> str: + json_data = await quart.request.json + + token = await self.ap.user_service.authenticate(json_data['user'], json_data['password']) + + return self.success(data={ + 'token': token + }) + + @self.route('/check-token', methods=['GET']) + async def _() -> str: + return self.success() diff --git a/pkg/api/http/controller/main.py b/pkg/api/http/controller/main.py new file mode 100644 index 00000000..acbfa104 --- /dev/null +++ b/pkg/api/http/controller/main.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +import asyncio +import os + +import quart +import quart_cors + +from ....core import app, entities as core_entities +from .groups import logs, system, settings, plugins, stats, user +from . import group + + +class HTTPController: + + ap: app.Application + + quart_app: quart.Quart + + def __init__(self, ap: app.Application) -> None: + self.ap = ap + self.quart_app = quart.Quart(__name__) + quart_cors.cors(self.quart_app, allow_origin="*") + + async def initialize(self) -> None: + await self.register_routes() + + async def run(self) -> None: + if self.ap.system_cfg.data["http-api"]["enable"]: + + async def shutdown_trigger_placeholder(): + while True: + await asyncio.sleep(1) + + async def exception_handler(*args, **kwargs): + try: + await self.quart_app.run_task( + *args, **kwargs + ) + except Exception as e: + self.ap.logger.error(f"启动 HTTP 服务失败: {e}") + + self.ap.task_mgr.create_task( + exception_handler( + host=self.ap.system_cfg.data["http-api"]["host"], + port=self.ap.system_cfg.data["http-api"]["port"], + shutdown_trigger=shutdown_trigger_placeholder, + ), + name="http-api-quart", + scopes=[core_entities.LifecycleControlScope.APPLICATION], + ) + + # await asyncio.sleep(5) + + async def register_routes(self) -> None: + + @self.quart_app.route("/healthz") + async def healthz(): + return {"code": 0, "msg": "ok"} + + for g in group.preregistered_groups: + ginst = g(self.ap, self.quart_app) + await ginst.initialize() + + frontend_path = "web/dist" + + @self.quart_app.route("/") + async def index(): + return await quart.send_from_directory(frontend_path, "index.html") + + @self.quart_app.route("/") + async def static_file(path: str): + return await quart.send_from_directory(frontend_path, path) diff --git a/pkg/api/http/service/__init__.py b/pkg/api/http/service/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pkg/api/http/service/user.py b/pkg/api/http/service/user.py new file mode 100644 index 00000000..b1d00a73 --- /dev/null +++ b/pkg/api/http/service/user.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +import sqlalchemy +import argon2 +import jwt +import datetime + +from ....core import app +from ....persistence.entities import user +from ....utils import constants + + +class UserService: + + ap: app.Application + + def __init__(self, ap: app.Application) -> None: + self.ap = ap + + async def is_initialized(self) -> bool: + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(user.User).limit(1) + ) + + result_list = result.all() + return result_list is not None and len(result_list) > 0 + + async def create_user(self, user_email: str, password: str) -> None: + ph = argon2.PasswordHasher() + + hashed_password = ph.hash(password) + + await self.ap.persistence_mgr.execute_async( + sqlalchemy.insert(user.User).values( + user=user_email, + password=hashed_password + ) + ) + + async def authenticate(self, user_email: str, password: str) -> str | None: + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(user.User).where(user.User.user == user_email) + ) + + result_list = result.all() + + if result_list is None or len(result_list) == 0: + raise ValueError('用户不存在') + + user_obj = result_list[0] + + ph = argon2.PasswordHasher() + + if not ph.verify(user_obj.password, password): + raise ValueError('密码错误') + + return await self.generate_jwt_token(user_email) + + async def generate_jwt_token(self, user_email: str) -> str: + jwt_secret = self.ap.instance_secret_meta.data['jwt_secret'] + jwt_expire = self.ap.system_cfg.data['http-api']['jwt-expire'] + + payload = { + 'user': user_email, + 'iss': 'LangBot-'+constants.edition, + 'exp': datetime.datetime.now() + datetime.timedelta(seconds=jwt_expire) + } + + return jwt.encode(payload, jwt_secret, algorithm='HS256') + + async def verify_jwt_token(self, token: str) -> str: + jwt_secret = self.ap.instance_secret_meta.data['jwt_secret'] + + return jwt.decode(token, jwt_secret, algorithms=['HS256'])['user'] diff --git a/pkg/audit/center/apigroup.py b/pkg/audit/center/apigroup.py index 26eac7ae..4b20a09a 100644 --- a/pkg/audit/center/apigroup.py +++ b/pkg/audit/center/apigroup.py @@ -9,11 +9,12 @@ import aiohttp import requests -from ...core import app +from ...core import app, entities as core_entities class APIGroup(metaclass=abc.ABCMeta): """API 组抽象类""" + _basic_info: dict = None _runtime_info: dict = None @@ -32,33 +33,28 @@ async def _do( data: dict = None, params: dict = None, headers: dict = {}, - **kwargs + **kwargs, ): """ 执行请求 """ - self._runtime_info['account_id'] = "-1" - + self._runtime_info["account_id"] = "-1" + url = self.prefix + path data = json.dumps(data) - headers['Content-Type'] = 'application/json' + headers["Content-Type"] = "application/json" try: async with aiohttp.ClientSession() as session: async with session.request( - method, - url, - data=data, - params=params, - headers=headers, - **kwargs + method, url, data=data, params=params, headers=headers, **kwargs ) as resp: self.ap.logger.debug("data: %s", data) self.ap.logger.debug("ret: %s", await resp.text()) except Exception as e: - self.ap.logger.debug(f'上报失败: {e}') - + self.ap.logger.debug(f"上报失败: {e}") + async def do( self, method: str, @@ -66,27 +62,27 @@ async def do( data: dict = None, params: dict = None, headers: dict = {}, - **kwargs + **kwargs, ) -> asyncio.Task: """执行请求""" - asyncio.create_task(self._do(method, path, data, params, headers, **kwargs)) - def gen_rid( - self - ): + return self.ap.task_mgr.create_task( + self._do(method, path, data, params, headers, **kwargs), + kind="telemetry-operation", + name=f"{method} {path}", + scopes=[core_entities.LifecycleControlScope.APPLICATION], + ).task + + def gen_rid(self): """生成一个请求 ID""" return str(uuid.uuid4()) - def basic_info( - self - ): + def basic_info(self): """获取基本信息""" basic_info = APIGroup._basic_info.copy() - basic_info['rid'] = self.gen_rid() + basic_info["rid"] = self.gen_rid() return basic_info - - def runtime_info( - self - ): + + def runtime_info(self): """获取运行时信息""" return APIGroup._runtime_info diff --git a/pkg/command/entities.py b/pkg/command/entities.py index 86975510..463dfe38 100644 --- a/pkg/command/entities.py +++ b/pkg/command/entities.py @@ -3,10 +3,10 @@ import typing import pydantic -import mirai from ..core import app, entities as core_entities from . import errors, operator +from ..platform.types import message as platform_message class CommandReturn(pydantic.BaseModel): @@ -17,7 +17,7 @@ class CommandReturn(pydantic.BaseModel): """文本 """ - image: typing.Optional[mirai.Image] = None + image: typing.Optional[platform_message.Image] = None """弃用""" image_url: typing.Optional[str] = None diff --git a/pkg/command/operators/func.py b/pkg/command/operators/func.py index 33031bfb..404813eb 100644 --- a/pkg/command/operators/func.py +++ b/pkg/command/operators/func.py @@ -2,6 +2,7 @@ from typing import AsyncGenerator from .. import operator, entities, cmdmgr +from ...plugin import context as plugin_context @operator.operator_class(name="func", help="查看所有已注册的内容函数", usage='!func') @@ -9,16 +10,18 @@ class FuncOperator(operator.CommandOperator): async def execute( self, context: entities.ExecuteContext ) -> AsyncGenerator[entities.CommandReturn, None]: - reply_str = "当前已加载的内容函数: \n\n" + reply_str = "当前已启用的内容函数: \n\n" index = 1 - all_functions = await self.ap.tool_mgr.get_all_functions() + all_functions = await self.ap.tool_mgr.get_all_functions( + plugin_enabled=True, + plugin_status=plugin_context.RuntimeContainerStatus.INITIALIZED, + ) for func in all_functions: - reply_str += "{}. {}{}:\n{}\n\n".format( + reply_str += "{}. {}:\n{}\n\n".format( index, - ("(已禁用) " if not func.enable else ""), func.name, func.description, ) diff --git a/pkg/command/operators/plugin.py b/pkg/command/operators/plugin.py index b1cf6ee1..e50d0ba2 100644 --- a/pkg/command/operators/plugin.py +++ b/pkg/command/operators/plugin.py @@ -18,7 +18,7 @@ async def execute( context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - plugin_list = self.ap.plugin_mgr.plugins + plugin_list = self.ap.plugin_mgr.plugins() reply_str = "所有插件({}):\n".format(len(plugin_list)) idx = 0 for plugin in plugin_list: @@ -110,7 +110,7 @@ async def execute( try: plugins = [ p.plugin_name - for p in self.ap.plugin_mgr.plugins + for p in self.ap.plugin_mgr.plugins() ] if plugins: @@ -163,24 +163,6 @@ async def execute( yield entities.CommandReturn(error=errors.CommandError("插件删除失败: "+str(e))) -async def update_plugin_status(plugin_name: str, new_status: bool, ap: app.Application): - if ap.plugin_mgr.get_plugin_by_name(plugin_name) is not None: - for plugin in ap.plugin_mgr.plugins: - if plugin.plugin_name == plugin_name: - plugin.enabled = new_status - - for func in plugin.content_functions: - func.enable = new_status - - await ap.plugin_mgr.setting.dump_container_setting(ap.plugin_mgr.plugins) - - break - - return True - else: - return False - - @operator.operator_class( name="on", help="启用插件", @@ -200,7 +182,7 @@ async def execute( plugin_name = context.crt_params[0] try: - if await update_plugin_status(plugin_name, True, self.ap): + if await self.ap.plugin_mgr.update_plugin_switch(plugin_name, True): yield entities.CommandReturn(text="已启用插件: {}".format(plugin_name)) else: yield entities.CommandReturn(error=errors.CommandError("插件状态修改失败: 未找到插件 {}".format(plugin_name))) @@ -228,7 +210,7 @@ async def execute( plugin_name = context.crt_params[0] try: - if await update_plugin_status(plugin_name, False, self.ap): + if await self.ap.plugin_mgr.update_plugin_switch(plugin_name, False): yield entities.CommandReturn(text="已禁用插件: {}".format(plugin_name)) else: yield entities.CommandReturn(error=errors.CommandError("插件状态修改失败: 未找到插件 {}".format(plugin_name))) diff --git a/pkg/config/manager.py b/pkg/config/manager.py index 88ed6525..4421003c 100644 --- a/pkg/config/manager.py +++ b/pkg/config/manager.py @@ -4,11 +4,19 @@ from .impls import pymodule, json as json_file, yaml as yaml_file -managers: ConfigManager = [] - - class ConfigManager: """配置文件管理器""" + + name: str = None + """配置管理器名""" + + description: str = None + """配置管理器描述""" + + schema: dict = None + """配置文件 schema + 需要符合 JSON Schema Draft 7 规范 + """ file: file_model.ConfigFile = None """配置文件实例""" @@ -16,6 +24,9 @@ class ConfigManager: data: dict = None """配置数据""" + doc_link: str = None + """配置文件文档链接""" + def __init__(self, cfg_file: file_model.ConfigFile) -> None: self.file = cfg_file self.data = {} diff --git a/pkg/config/settings.py b/pkg/config/settings.py new file mode 100644 index 00000000..1f21e926 --- /dev/null +++ b/pkg/config/settings.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +from . import manager as config_manager +from ..core import app + + +class SettingsManager: + """设置管理器 + 保存、管理多个配置文件管理器 + """ + + ap: app.Application + + managers: list[config_manager.ConfigManager] = [] + """配置文件管理器列表""" + + def __init__(self, ap: app.Application) -> None: + self.ap = ap + self.managers = [] + + async def initialize(self) -> None: + pass + + def register_manager( + self, + name: str, + description: str, + manager: config_manager.ConfigManager, + schema: dict=None, + doc_link: str=None, + ) -> None: + """注册配置管理器 + + Args: + name (str): 配置管理器名 + description (str): 配置管理器描述 + manager (ConfigManager): 配置管理器 + schema (dict): 配置文件 schema,符合 JSON Schema Draft 7 规范 + """ + + for m in self.managers: + if m.name == name: + raise ValueError(f'配置管理器名 {name} 已存在') + + manager.name = name + manager.description = description + manager.schema = schema + manager.doc_link = doc_link + self.managers.append(manager) + + def get_manager(self, name: str) -> config_manager.ConfigManager | None: + """获取配置管理器 + + Args: + name (str): 配置管理器名 + + Returns: + ConfigManager: 配置管理器 + """ + + for m in self.managers: + if m.name == name: + return m + + return None + + def get_manager_list(self) -> list[config_manager.ConfigManager]: + """获取配置管理器列表 + + Returns: + list[ConfigManager]: 配置管理器列表 + """ + + return self.managers + diff --git a/pkg/core/app.py b/pkg/core/app.py index e6e25ea0..06d8c1bc 100644 --- a/pkg/core/app.py +++ b/pkg/core/app.py @@ -2,7 +2,11 @@ import logging import asyncio +import threading import traceback +import enum +import sys +import os from ..platform import manager as im_mgr from ..provider.session import sessionmgr as llm_session_mgr @@ -11,17 +15,29 @@ from ..provider.tools import toolmgr as llm_tool_mgr from ..provider import runnermgr from ..config import manager as config_mgr +from ..config import settings as settings_mgr from ..audit.center import v2 as center_mgr from ..command import cmdmgr from ..plugin import manager as plugin_mgr from ..pipeline import pool from ..pipeline import controller, stagemgr from ..utils import version as version_mgr, proxy as proxy_mgr, announce as announce_mgr +from ..persistence import mgr as persistencemgr +from ..api.http.controller import main as http_controller +from ..api.http.service import user as user_service +from ..utils import logcache, ip +from . import taskmgr +from . import entities as core_entities class Application: """运行时应用对象和上下文""" + event_loop: asyncio.AbstractEventLoop = None + + # asyncio_tasks: list[asyncio.Task] = [] + task_mgr: taskmgr.AsyncTaskManager = None + platform_mgr: im_mgr.PlatformManager = None cmd_mgr: cmdmgr.CommandManager = None @@ -36,6 +52,8 @@ class Application: runner_mgr: runnermgr.RunnerManager = None + settings_mgr: settings_mgr.SettingsManager = None + # ======= 配置管理器 ======= command_cfg: config_mgr.ConfigManager = None @@ -58,6 +76,8 @@ class Application: llm_models_meta: config_mgr.ConfigManager = None + instance_secret_meta: config_mgr.ConfigManager = None + # ========================= ctr_mgr: center_mgr.V2CenterAPI = None @@ -78,6 +98,16 @@ class Application: logger: logging.Logger = None + persistence_mgr: persistencemgr.PersistenceManager = None + + http_ctrl: http_controller.HTTPController = None + + log_cache: logcache.LogCache = None + + # ========= HTTP Services ========= + + user_service: user_service.UserService = None + def __init__(self): pass @@ -85,34 +115,89 @@ async def initialize(self): pass async def run(self): - await self.plugin_mgr.initialize_plugins() + try: + await self.plugin_mgr.initialize_plugins() + # 后续可能会允许动态重启其他任务 + # 故为了防止程序在非 Ctrl-C 情况下退出,这里创建一个不会结束的协程 + async def never_ending(): + while True: + await asyncio.sleep(1) + + self.task_mgr.create_task(self.platform_mgr.run(), name="platform-manager", scopes=[core_entities.LifecycleControlScope.APPLICATION, core_entities.LifecycleControlScope.PLATFORM]) + self.task_mgr.create_task(self.ctrl.run(), name="query-controller", scopes=[core_entities.LifecycleControlScope.APPLICATION]) + self.task_mgr.create_task(self.http_ctrl.run(), name="http-api-controller", scopes=[core_entities.LifecycleControlScope.APPLICATION]) + self.task_mgr.create_task(never_ending(), name="never-ending-task", scopes=[core_entities.LifecycleControlScope.APPLICATION]) + + await self.print_web_access_info() + await self.task_mgr.wait_all() + except asyncio.CancelledError: + pass + except Exception as e: + self.logger.error(f"应用运行致命异常: {e}") + self.logger.debug(f"Traceback: {traceback.format_exc()}") - tasks = [] + async def print_web_access_info(self): + """打印访问 webui 的提示""" - try: - - tasks = [ - asyncio.create_task(self.platform_mgr.run()), - asyncio.create_task(self.ctrl.run()) - ] + if not os.path.exists(os.path.join(".", "web/dist")): + self.logger.warning("WebUI 文件缺失,请根据文档获取:https://docs.langbot.app/webui/intro.html") + return - # 挂信号处理 + import socket - import signal + host_ip = socket.gethostbyname(socket.gethostname()) - def signal_handler(sig, frame): - for task in tasks: - task.cancel() - self.logger.info("程序退出.") - exit(0) + public_ip = await ip.get_myip() - signal.signal(signal.SIGINT, signal_handler) + port = self.system_cfg.data['http-api']['port'] - await asyncio.gather(*tasks, return_exceptions=True) + tips = f""" +======================================= +✨ 您可通过以下方式访问管理面板 - except asyncio.CancelledError: - pass - except Exception as e: - self.logger.error(f"应用运行致命异常: {e}") - self.logger.debug(f"Traceback: {traceback.format_exc()}") +🏠 本地地址:http://{host_ip}:{port}/ +🌐 公网地址:http://{public_ip}:{port}/ + +📌 如果您在容器中运行此程序,请确保容器的 {port} 端口已对外暴露 +🔗 若要使用公网地址访问,请阅读以下须知 + 1. 公网地址仅供参考,请以您的主机公网 IP 为准; + 2. 要使用公网地址访问,请确保您的主机具有公网 IP,并且系统防火墙已放行 {port} 端口; + +🤯 WebUI 仍处于 Beta 测试阶段,如有问题或建议请反馈到 https://github.com/RockChinQ/LangBot/issues +======================================= +""".strip() + for line in tips.split("\n"): + self.logger.info(line) + + async def reload( + self, + scope: core_entities.LifecycleControlScope, + ): + match scope: + case core_entities.LifecycleControlScope.PLATFORM.value: + self.logger.info("执行热重载 scope="+scope) + await self.platform_mgr.shutdown() + + self.platform_mgr = im_mgr.PlatformManager(self) + + await self.platform_mgr.initialize() + + self.task_mgr.create_task(self.platform_mgr.run(), name="platform-manager", scopes=[core_entities.LifecycleControlScope.APPLICATION, core_entities.LifecycleControlScope.PLATFORM]) + case core_entities.LifecycleControlScope.PLUGIN.value: + self.logger.info("执行热重载 scope="+scope) + await self.plugin_mgr.destroy_plugins() + + # 删除 sys.module 中所有的 plugins/* 下的模块 + for mod in list(sys.modules.keys()): + if mod.startswith("plugins."): + del sys.modules[mod] + + self.plugin_mgr = plugin_mgr.PluginManager(self) + await self.plugin_mgr.initialize() + + await self.plugin_mgr.initialize_plugins() + await self.plugin_mgr.load_plugins() + await self.plugin_mgr.initialize_plugins() + case _: + pass diff --git a/pkg/core/boot.py b/pkg/core/boot.py index 5663c2ef..e6a0e3eb 100644 --- a/pkg/core/boot.py +++ b/pkg/core/boot.py @@ -1,10 +1,13 @@ from __future__ import print_function import traceback +import asyncio +import os from . import app from ..audit import identifier from . import stage +from ..utils import constants # 引入启动阶段实现以便注册 from .stages import load_config, setup_logger, build_app, migrate, show_notes @@ -19,13 +22,19 @@ ] -async def make_app() -> app.Application: +async def make_app(loop: asyncio.AbstractEventLoop) -> app.Application: # 生成标识符 identifier.init() + # 确定是否为调试模式 + if "DEBUG" in os.environ and os.environ["DEBUG"] in ["true", "1"]: + constants.debug_mode = True + ap = app.Application() + ap.event_loop = loop + # 执行启动阶段 for stage_name in stage_order: stage_cls = stage.preregistered_stages[stage_name] @@ -38,9 +47,23 @@ async def make_app() -> app.Application: return ap -async def main(): +async def main(loop: asyncio.AbstractEventLoop): try: - app_inst = await make_app() + + # 挂系统信号处理 + import signal + + ap: app.Application + + def signal_handler(sig, frame): + print("[Signal] 程序退出.") + # ap.shutdown() + os._exit(0) + + signal.signal(signal.SIGINT, signal_handler) + + app_inst = await make_app(loop) + ap = app_inst await app_inst.run() except Exception as e: traceback.print_exc() diff --git a/pkg/core/bootutils/deps.py b/pkg/core/bootutils/deps.py index e7df3e82..d83ed7a9 100644 --- a/pkg/core/bootutils/deps.py +++ b/pkg/core/bootutils/deps.py @@ -5,9 +5,8 @@ "openai": "openai", "anthropic": "anthropic", "colorlog": "colorlog", - "mirai": "yiri-mirai-rc", "aiocqhttp": "aiocqhttp", - "botpy": "qq-botpy", + "botpy": "qq-botpy-rc", "PIL": "pillow", "nakuru": "nakuru-project-idk", "tiktoken": "tiktoken", @@ -16,6 +15,14 @@ "psutil": "psutil", "async_lru": "async-lru", "ollama": "ollama", + "quart": "quart", + "quart_cors": "quart-cors", + "sqlalchemy": "sqlalchemy[asyncio]", + "aiosqlite": "aiosqlite", + "aiofiles": "aiofiles", + "aioshutil": "aioshutil", + "argon2": "argon2-cffi", + "jwt": "pyjwt", } diff --git a/pkg/core/bootutils/log.py b/pkg/core/bootutils/log.py index d7b6da0a..ce53efd5 100644 --- a/pkg/core/bootutils/log.py +++ b/pkg/core/bootutils/log.py @@ -5,6 +5,8 @@ import colorlog +from ...utils import constants + log_colors_config = { "DEBUG": "green", # cyan white @@ -15,18 +17,18 @@ } -async def init_logging() -> logging.Logger: +async def init_logging(extra_handlers: list[logging.Handler] = None) -> logging.Logger: # 删除所有现有的logger for handler in logging.root.handlers[:]: logging.root.removeHandler(handler) level = logging.INFO - if "DEBUG" in os.environ and os.environ["DEBUG"] in ["true", "1"]: + if constants.debug_mode: level = logging.DEBUG - log_file_name = "data/logs/qcg-%s.log" % time.strftime( - "%Y-%m-%d-%H-%M-%S", time.localtime() + log_file_name = "data/logs/langbot-%s.log" % time.strftime( + "%Y-%m-%d", time.localtime() ) qcg_logger = logging.getLogger("qcg") @@ -34,14 +36,15 @@ async def init_logging() -> logging.Logger: qcg_logger.setLevel(level) color_formatter = colorlog.ColoredFormatter( - fmt="%(log_color)s[%(asctime)s.%(msecs)03d] %(pathname)s (%(lineno)d) - [%(levelname)s] :\n %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", + fmt="%(log_color)s[%(asctime)s.%(msecs)03d] %(filename)s (%(lineno)d) - [%(levelname)s] : %(message)s", + datefmt="%m-%d %H:%M:%S", log_colors=log_colors_config, ) stream_handler = logging.StreamHandler(sys.stdout) - log_handlers: logging.Handler = [stream_handler, logging.FileHandler(log_file_name)] + log_handlers: list[logging.Handler] = [stream_handler, logging.FileHandler(log_file_name)] + log_handlers += extra_handlers if extra_handlers is not None else [] for handler in log_handlers: handler.setLevel(level) diff --git a/pkg/core/entities.py b/pkg/core/entities.py index 6305a0ec..464384be 100644 --- a/pkg/core/entities.py +++ b/pkg/core/entities.py @@ -6,13 +6,23 @@ import asyncio import pydantic -import mirai from ..provider import entities as llm_entities from ..provider.modelmgr import entities from ..provider.sysprompt import entities as sysprompt_entities from ..provider.tools import entities as tools_entities from ..platform import adapter as msadapter +from ..platform.types import message as platform_message +from ..platform.types import events as platform_events +from ..platform.types import entities as platform_entities + + + +class LifecycleControlScope(enum.Enum): + + APPLICATION = "application" + PLATFORM = "platform" + PLUGIN = "plugin" class LauncherTypes(enum.Enum): @@ -40,10 +50,10 @@ class Query(pydantic.BaseModel): sender_id: int """发送者ID,platform处理阶段设置""" - message_event: mirai.MessageEvent + message_event: platform_events.MessageEvent """事件,platform收到的原始事件""" - message_chain: mirai.MessageChain + message_chain: platform_message.MessageChain """消息链,platform收到的原始消息链""" adapter: msadapter.MessageSourceAdapter @@ -67,10 +77,10 @@ class Query(pydantic.BaseModel): use_funcs: typing.Optional[list[tools_entities.LLMFunction]] = None """使用的函数,由前置处理器阶段设置""" - resp_messages: typing.Optional[list[llm_entities.Message]] | typing.Optional[list[mirai.MessageChain]] = [] + resp_messages: typing.Optional[list[llm_entities.Message]] | typing.Optional[list[platform_message.MessageChain]] = [] """由Process阶段生成的回复消息对象列表""" - resp_message_chain: typing.Optional[list[mirai.MessageChain]] = None + resp_message_chain: typing.Optional[list[platform_message.MessageChain]] = None """回复消息链,从resp_messages包装而得""" # ======= 内部保留 ======= @@ -108,7 +118,7 @@ class Session(pydantic.BaseModel): using_conversation: typing.Optional[Conversation] = None - conversations: typing.Optional[list[Conversation]] = [] + conversations: typing.Optional[list[Conversation]] = pydantic.Field(default_factory=list) create_time: typing.Optional[datetime.datetime] = pydantic.Field(default_factory=datetime.datetime.now) diff --git a/pkg/core/migrations/m013_http_api_config.py b/pkg/core/migrations/m013_http_api_config.py new file mode 100644 index 00000000..c5fe55ba --- /dev/null +++ b/pkg/core/migrations/m013_http_api_config.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +from .. import migration + + +@migration.migration_class("http-api-config", 13) +class HttpApiConfigMigration(migration.Migration): + """迁移""" + + async def need_migrate(self) -> bool: + """判断当前环境是否需要运行此迁移""" + return 'http-api' not in self.ap.system_cfg.data or "persistence" not in self.ap.system_cfg.data + + async def run(self): + """执行迁移""" + + self.ap.system_cfg.data['http-api'] = { + "enable": True, + "host": "0.0.0.0", + "port": 5300, + "jwt-expire": 604800 + } + + self.ap.system_cfg.data['persistence'] = { + "sqlite": { + "path": "data/persistence.db" + }, + "use": "sqlite" + } + + await self.ap.system_cfg.dump_config() diff --git a/pkg/core/migrations/m014_force_delay_config.py b/pkg/core/migrations/m014_force_delay_config.py new file mode 100644 index 00000000..55521c9c --- /dev/null +++ b/pkg/core/migrations/m014_force_delay_config.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from .. import migration + + +@migration.migration_class("force-delay-config", 14) +class ForceDelayConfigMigration(migration.Migration): + """迁移""" + + async def need_migrate(self) -> bool: + """判断当前环境是否需要运行此迁移""" + return type(self.ap.platform_cfg.data['force-delay']) == list + + async def run(self): + """执行迁移""" + + self.ap.platform_cfg.data['force-delay'] = { + "min": self.ap.platform_cfg.data['force-delay'][0], + "max": self.ap.platform_cfg.data['force-delay'][1] + } + + await self.ap.platform_cfg.dump_config() diff --git a/pkg/core/stages/build_app.py b/pkg/core/stages/build_app.py index c0e731ce..c5f5c005 100644 --- a/pkg/core/stages/build_app.py +++ b/pkg/core/stages/build_app.py @@ -15,6 +15,12 @@ from ...provider.tools import toolmgr as llm_tool_mgr from ...provider import runnermgr from ...platform import manager as im_mgr +from ...persistence import mgr as persistencemgr +from ...api.http.controller import main as http_controller +from ...api.http.service import user as user_service +from ...utils import logcache +from .. import taskmgr + @stage.stage_class("BuildAppStage") class BuildAppStage(stage.BootingStage): @@ -24,6 +30,7 @@ class BuildAppStage(stage.BootingStage): async def run(self, ap: app.Application): """构建app对象的各个组件对象并初始化 """ + ap.task_mgr = taskmgr.AsyncTaskManager(ap) proxy_mgr = proxy.ProxyManager(ap) await proxy_mgr.initialize() @@ -58,6 +65,13 @@ async def run(self, ap: app.Application): ap.query_pool = pool.QueryPool() + log_cache = logcache.LogCache() + ap.log_cache = log_cache + + persistence_mgr_inst = persistencemgr.PersistenceManager(ap) + await persistence_mgr_inst.initialize() + ap.persistence_mgr = persistence_mgr_inst + plugin_mgr_inst = plugin_mgr.PluginManager(ap) await plugin_mgr_inst.initialize() ap.plugin_mgr = plugin_mgr_inst @@ -95,6 +109,12 @@ async def run(self, ap: app.Application): await stage_mgr.initialize() ap.stage_mgr = stage_mgr + http_ctrl = http_controller.HTTPController(ap) + await http_ctrl.initialize() + ap.http_ctrl = http_ctrl + + user_service_inst = user_service.UserService(ap) + ap.user_service = user_service_inst ctrl = controller.Controller(ap) ap.ctrl = ctrl diff --git a/pkg/core/stages/load_config.py b/pkg/core/stages/load_config.py index cb6e1ed0..cc154a7c 100644 --- a/pkg/core/stages/load_config.py +++ b/pkg/core/stages/load_config.py @@ -1,7 +1,11 @@ from __future__ import annotations +import secrets + from .. import stage, app from ..bootutils import config +from ...config import settings as settings_mgr +from ...utils import schema @stage.stage_class("LoadConfigStage") @@ -12,12 +16,56 @@ class LoadConfigStage(stage.BootingStage): async def run(self, ap: app.Application): """启动 """ + + ap.settings_mgr = settings_mgr.SettingsManager(ap) + await ap.settings_mgr.initialize() + ap.command_cfg = await config.load_json_config("data/config/command.json", "templates/command.json", completion=False) ap.pipeline_cfg = await config.load_json_config("data/config/pipeline.json", "templates/pipeline.json", completion=False) ap.platform_cfg = await config.load_json_config("data/config/platform.json", "templates/platform.json", completion=False) ap.provider_cfg = await config.load_json_config("data/config/provider.json", "templates/provider.json", completion=False) ap.system_cfg = await config.load_json_config("data/config/system.json", "templates/system.json", completion=False) + ap.settings_mgr.register_manager( + name="command.json", + description="命令配置", + manager=ap.command_cfg, + schema=schema.CONFIG_COMMAND_SCHEMA, + doc_link="https://docs.langbot.app/config/function/command.html" + ) + + ap.settings_mgr.register_manager( + name="pipeline.json", + description="消息处理流水线配置", + manager=ap.pipeline_cfg, + schema=schema.CONFIG_PIPELINE_SCHEMA, + doc_link="https://docs.langbot.app/config/function/pipeline.html" + ) + + ap.settings_mgr.register_manager( + name="platform.json", + description="消息平台配置", + manager=ap.platform_cfg, + schema=schema.CONFIG_PLATFORM_SCHEMA, + doc_link="https://docs.langbot.app/config/function/platform.html" + ) + + ap.settings_mgr.register_manager( + name="provider.json", + description="大模型能力配置", + manager=ap.provider_cfg, + schema=schema.CONFIG_PROVIDER_SCHEMA, + doc_link="https://docs.langbot.app/config/function/provider.html" + ) + + ap.settings_mgr.register_manager( + name="system.json", + description="系统配置", + manager=ap.system_cfg, + schema=schema.CONFIG_SYSTEM_SCHEMA, + doc_link="https://docs.langbot.app/config/function/system.html" + ) + ap.plugin_setting_meta = await config.load_json_config("plugins/plugins.json", "templates/plugin-settings.json") await ap.plugin_setting_meta.dump_config() @@ -29,3 +77,8 @@ async def run(self, ap: app.Application): ap.llm_models_meta = await config.load_json_config("data/metadata/llm-models.json", "templates/metadata/llm-models.json") await ap.llm_models_meta.dump_config() + + ap.instance_secret_meta = await config.load_json_config("data/metadata/instance-secret.json", template_data={ + 'jwt_secret': secrets.token_hex(16) + }) + await ap.instance_secret_meta.dump_config() diff --git a/pkg/core/stages/migrate.py b/pkg/core/stages/migrate.py index 92735c90..dd9023ed 100644 --- a/pkg/core/stages/migrate.py +++ b/pkg/core/stages/migrate.py @@ -6,7 +6,7 @@ from .. import migration from ..migrations import m001_sensitive_word_migration, m002_openai_config_migration, m003_anthropic_requester_cfg_completion, m004_moonshot_cfg_completion from ..migrations import m005_deepseek_cfg_completion, m006_vision_config, m007_qcg_center_url, m008_ad_fixwin_config_migrate, m009_msg_truncator_cfg -from ..migrations import m010_ollama_requester_config, m011_command_prefix_config, m012_runner_config +from ..migrations import m010_ollama_requester_config, m011_command_prefix_config, m012_runner_config, m013_http_api_config, m014_force_delay_config @stage.stage_class("MigrationStage") diff --git a/pkg/core/stages/setup_logger.py b/pkg/core/stages/setup_logger.py index 02446b85..8f385d1f 100644 --- a/pkg/core/stages/setup_logger.py +++ b/pkg/core/stages/setup_logger.py @@ -1,9 +1,38 @@ from __future__ import annotations +import logging +import asyncio +from datetime import datetime + from .. import stage, app from ..bootutils import log +class PersistenceHandler(logging.Handler, object): + """ + 保存日志到数据库 + """ + ap: app.Application + + def __init__(self, name, ap: app.Application): + logging.Handler.__init__(self) + self.ap = ap + + def emit(self, record): + """ + emit函数为自定义handler类时必重写的函数,这里可以根据需要对日志消息做一些处理,比如发送日志到服务器 + + 发出记录(Emit a record) + """ + try: + msg = self.format(record) + if self.ap.log_cache is not None: + self.ap.log_cache.add_log(msg) + + except Exception: + self.handleError(record) + + @stage.stage_class("SetupLoggerStage") class SetupLoggerStage(stage.BootingStage): """设置日志器阶段 @@ -12,4 +41,9 @@ class SetupLoggerStage(stage.BootingStage): async def run(self, ap: app.Application): """启动 """ - ap.logger = await log.init_logging() + persistence_handler = PersistenceHandler('LoggerHandler', ap) + + extra_handlers = [] + extra_handlers = [persistence_handler] + + ap.logger = await log.init_logging(extra_handlers) diff --git a/pkg/core/taskmgr.py b/pkg/core/taskmgr.py new file mode 100644 index 00000000..2c029c03 --- /dev/null +++ b/pkg/core/taskmgr.py @@ -0,0 +1,235 @@ +from __future__ import annotations + +import asyncio +import typing +import datetime +import traceback + +from . import app +from . import entities as core_entities + + +class TaskContext: + """任务跟踪上下文""" + + current_action: str + """当前正在执行的动作""" + + log: str + """记录日志""" + + def __init__(self): + self.current_action = "default" + self.log = "" + + def _log(self, msg: str): + self.log += msg + "\n" + + def set_current_action(self, action: str): + self.current_action = action + + def trace( + self, + msg: str, + action: str = None, + ): + if action is not None: + self.set_current_action(action) + + self._log( + f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | {self.current_action} | {msg}" + ) + + def to_dict(self) -> dict: + return {"current_action": self.current_action, "log": self.log} + + @staticmethod + def new() -> TaskContext: + return TaskContext() + + @staticmethod + def placeholder() -> TaskContext: + global placeholder_context + + if placeholder_context is None: + placeholder_context = TaskContext() + + return placeholder_context + + +placeholder_context: TaskContext | None = None + + +class TaskWrapper: + """任务包装器""" + + _id_index: int = 0 + """任务ID索引""" + + id: int + """任务ID""" + + task_type: str = "system" # 任务类型: system 或 user + """任务类型""" + + kind: str = "system_task" # 由发起者确定任务种类,通常同质化的任务种类相同 + """任务种类""" + + name: str = "" + """任务唯一名称""" + + label: str = "" + """任务显示名称""" + + task_context: TaskContext + """任务上下文""" + + task: asyncio.Task + """任务""" + + task_stack: list = None + """任务堆栈""" + + ap: app.Application + """应用实例""" + + scopes: list[core_entities.LifecycleControlScope] + """任务所属生命周期控制范围""" + + def __init__( + self, + ap: app.Application, + coro: typing.Coroutine, + task_type: str = "system", + kind: str = "system_task", + name: str = "", + label: str = "", + context: TaskContext = None, + scopes: list[core_entities.LifecycleControlScope] = [core_entities.LifecycleControlScope.APPLICATION], + ): + self.id = TaskWrapper._id_index + TaskWrapper._id_index += 1 + self.ap = ap + self.task_context = context or TaskContext() + self.task = self.ap.event_loop.create_task(coro) + self.task_type = task_type + self.kind = kind + self.name = name + self.label = label if label != "" else name + self.task.set_name(name) + self.scopes = scopes + + def assume_exception(self): + try: + exception = self.task.exception() + if self.task_stack is None: + self.task_stack = self.task.get_stack() + return exception + except: + return None + + def assume_result(self): + try: + return self.task.result() + except: + return None + + def to_dict(self) -> dict: + + exception_traceback = None + if self.assume_exception() is not None: + exception_traceback = 'Traceback (most recent call last):\n' + + for frame in self.task_stack: + exception_traceback += f" File \"{frame.f_code.co_filename}\", line {frame.f_lineno}, in {frame.f_code.co_name}\n" + + exception_traceback += f" {self.assume_exception().__str__()}\n" + + return { + "id": self.id, + "task_type": self.task_type, + "kind": self.kind, + "name": self.name, + "label": self.label, + "scopes": [scope.value for scope in self.scopes], + "task_context": self.task_context.to_dict(), + "runtime": { + "done": self.task.done(), + "state": self.task._state, + "exception": self.assume_exception().__str__() if self.assume_exception() is not None else None, + "exception_traceback": exception_traceback, + "result": self.assume_result().__str__() if self.assume_result() is not None else None, + }, + } + + def cancel(self): + self.task.cancel() + + +class AsyncTaskManager: + """保存app中的所有异步任务 + 包含系统级的和用户级(插件安装、更新等由用户直接发起的)的""" + + ap: app.Application + + tasks: list[TaskWrapper] + """所有任务""" + + def __init__(self, ap: app.Application): + self.ap = ap + self.tasks = [] + + def create_task( + self, + coro: typing.Coroutine, + task_type: str = "system", + kind: str = "system-task", + name: str = "", + label: str = "", + context: TaskContext = None, + scopes: list[core_entities.LifecycleControlScope] = [core_entities.LifecycleControlScope.APPLICATION], + ) -> TaskWrapper: + wrapper = TaskWrapper(self.ap, coro, task_type, kind, name, label, context, scopes) + self.tasks.append(wrapper) + return wrapper + + def create_user_task( + self, + coro: typing.Coroutine, + kind: str = "user-task", + name: str = "", + label: str = "", + context: TaskContext = None, + scopes: list[core_entities.LifecycleControlScope] = [core_entities.LifecycleControlScope.APPLICATION], + ) -> TaskWrapper: + return self.create_task(coro, "user", kind, name, label, context, scopes) + + async def wait_all(self): + await asyncio.gather(*[t.task for t in self.tasks], return_exceptions=True) + + def get_all_tasks(self) -> list[TaskWrapper]: + return self.tasks + + def get_tasks_dict( + self, + type: str = None, + ) -> dict: + return { + "tasks": [ + t.to_dict() for t in self.tasks if type is None or t.task_type == type + ], + "id_index": TaskWrapper._id_index, + } + + def get_task_by_id(self, id: int) -> TaskWrapper | None: + for t in self.tasks: + if t.id == id: + return t + return None + + def cancel_by_scope(self, scope: core_entities.LifecycleControlScope): + for wrapper in self.tasks: + + if not wrapper.task.done() and scope in wrapper.scopes: + + wrapper.task.cancel() diff --git a/pkg/persistence/__init__.py b/pkg/persistence/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pkg/persistence/database.py b/pkg/persistence/database.py new file mode 100644 index 00000000..0dd82817 --- /dev/null +++ b/pkg/persistence/database.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +import abc + +import sqlalchemy.ext.asyncio as sqlalchemy_asyncio + +from ..core import app + + +preregistered_managers: list[type[BaseDatabaseManager]] = [] + +def manager_class(name: str) -> None: + """注册一个数据库管理类""" + + def decorator(cls: type[BaseDatabaseManager]) -> type[BaseDatabaseManager]: + cls.name = name + preregistered_managers.append(cls) + return cls + + return decorator + + +class BaseDatabaseManager(abc.ABC): + """基础数据库管理类""" + + name: str + + ap: app.Application + + engine: sqlalchemy_asyncio.AsyncEngine + + def __init__(self, ap: app.Application) -> None: + self.ap = ap + + @abc.abstractmethod + async def initialize(self) -> None: + pass + + def get_engine(self) -> sqlalchemy_asyncio.AsyncEngine: + return self.engine diff --git a/pkg/persistence/databases/__init__.py b/pkg/persistence/databases/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pkg/persistence/databases/sqlite.py b/pkg/persistence/databases/sqlite.py new file mode 100644 index 00000000..14f89092 --- /dev/null +++ b/pkg/persistence/databases/sqlite.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +import sqlalchemy.ext.asyncio as sqlalchemy_asyncio + +from .. import database + + +@database.manager_class("sqlite") +class SQLiteDatabaseManager(database.BaseDatabaseManager): + """SQLite 数据库管理类""" + + async def initialize(self) -> None: + self.engine = sqlalchemy_asyncio.create_async_engine(f"sqlite+aiosqlite:///{self.ap.system_cfg.data['persistence']['sqlite']['path']}") diff --git a/pkg/persistence/entities/__init__.py b/pkg/persistence/entities/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pkg/persistence/entities/base.py b/pkg/persistence/entities/base.py new file mode 100644 index 00000000..b0d8b5db --- /dev/null +++ b/pkg/persistence/entities/base.py @@ -0,0 +1,5 @@ +import sqlalchemy.orm + + +class Base(sqlalchemy.orm.DeclarativeBase): + pass diff --git a/pkg/persistence/entities/user.py b/pkg/persistence/entities/user.py new file mode 100644 index 00000000..55597b4f --- /dev/null +++ b/pkg/persistence/entities/user.py @@ -0,0 +1,11 @@ +import sqlalchemy + +from .base import Base + + +class User(Base): + __tablename__ = 'users' + + id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True) + user = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) + password = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) diff --git a/pkg/persistence/mgr.py b/pkg/persistence/mgr.py new file mode 100644 index 00000000..0eef4800 --- /dev/null +++ b/pkg/persistence/mgr.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +import asyncio +import datetime + +import sqlalchemy.ext.asyncio as sqlalchemy_asyncio +import sqlalchemy + +from . import database +from .entities import user, base +from ..core import app +from .databases import sqlite + + +class PersistenceManager: + """持久化模块管理器""" + + ap: app.Application + + db: database.BaseDatabaseManager + """数据库管理器""" + + meta: sqlalchemy.MetaData + + def __init__(self, ap: app.Application): + self.ap = ap + self.meta = base.Base.metadata + + async def initialize(self): + + for manager in database.preregistered_managers: + self.db = manager(self.ap) + await self.db.initialize() + + await self.create_tables() + + async def create_tables(self): + # TODO: 对扩展友好 + + # 日志 + async with self.get_db_engine().connect() as conn: + await conn.run_sync(self.meta.create_all) + + await conn.commit() + + async def execute_async( + self, + *args, + **kwargs + ) -> sqlalchemy.engine.cursor.CursorResult: + async with self.get_db_engine().connect() as conn: + result = await conn.execute(*args, **kwargs) + await conn.commit() + return result + + def get_db_engine(self) -> sqlalchemy_asyncio.AsyncEngine: + return self.db.get_engine() diff --git a/pkg/pipeline/cntfilter/cntfilter.py b/pkg/pipeline/cntfilter/cntfilter.py index 29e66cc4..f7376b61 100644 --- a/pkg/pipeline/cntfilter/cntfilter.py +++ b/pkg/pipeline/cntfilter/cntfilter.py @@ -1,9 +1,5 @@ from __future__ import annotations -import mirai -import mirai.models -import mirai.models.message - from ...core import app from .. import stage, entities, stagemgr @@ -12,6 +8,9 @@ from . import filter as filter_model, entities as filter_entities from .filters import cntignore, banwords, baiduexamine from ...provider import entities as llm_entities +from ...platform.types import message as platform_message +from ...platform.types import events as platform_events +from ...platform.types import entities as platform_entities @stage.stage_class('PostContentFilterStage') @@ -89,8 +88,8 @@ async def _pre_process( elif result.level == filter_entities.ResultLevel.PASS: # 传到下一个 message = result.replacement - query.message_chain = mirai.MessageChain( - mirai.Plain(message) + query.message_chain = platform_message.MessageChain( + platform_message.Plain(message) ) return entities.StageProcessResult( @@ -148,7 +147,7 @@ async def process( contain_non_text = False - text_components = [mirai.Plain, mirai.models.message.Source] + text_components = [platform_message.Plain, platform_message.Source] for me in query.message_chain: if type(me) not in text_components: diff --git a/pkg/pipeline/controller.py b/pkg/pipeline/controller.py index 677db31d..3113b3bf 100644 --- a/pkg/pipeline/controller.py +++ b/pkg/pipeline/controller.py @@ -4,11 +4,10 @@ import typing import traceback -import mirai - from ..core import app, entities from . import entities as pipeline_entities from ..plugin import events +from ..platform.types import message as platform_message class Controller: @@ -59,8 +58,13 @@ async def _process_query(selected_query): (await self.ap.sess_mgr.get_session(selected_query)).semaphore.release() # 通知其他协程,有新的请求可以处理了 self.ap.query_pool.condition.notify_all() - - asyncio.create_task(_process_query(selected_query)) + self.ap.task_mgr.create_task( + _process_query(selected_query), + kind="query", + name=f"query-{selected_query.query_id}", + scopes=[entities.LifecycleControlScope.APPLICATION, entities.LifecycleControlScope.PLATFORM], + ) + except Exception as e: # traceback.print_exc() self.ap.logger.error(f"控制器循环出错: {e}") @@ -73,11 +77,11 @@ async def _check_output(self, query: entities.Query, result: pipeline_entities.S # 处理str类型 if isinstance(result.user_notice, str): - result.user_notice = mirai.MessageChain( - mirai.Plain(result.user_notice) + result.user_notice = platform_message.MessageChain( + platform_message.Plain(result.user_notice) ) elif isinstance(result.user_notice, list): - result.user_notice = mirai.MessageChain( + result.user_notice = platform_message.MessageChain( *result.user_notice ) @@ -159,6 +163,23 @@ async def _execute_from_stage( async def process_query(self, query: entities.Query): """处理请求 """ + + # ======== 触发 MessageReceived 事件 ======== + event_type = events.PersonMessageReceived if query.launcher_type == entities.LauncherTypes.PERSON else events.GroupMessageReceived + + event_ctx = await self.ap.plugin_mgr.emit_event( + event=event_type( + launcher_type=query.launcher_type.value, + launcher_id=query.launcher_id, + sender_id=query.sender_id, + message_chain=query.message_chain, + query=query + ) + ) + + if event_ctx.is_prevented_default(): + return + self.ap.logger.debug(f"Processing query {query}") try: @@ -166,7 +187,6 @@ async def process_query(self, query: entities.Query): except Exception as e: self.ap.logger.error(f"处理请求时出错 query_id={query.query_id} stage={query.current_stage.inst_name} : {e}") self.ap.logger.debug(f"Traceback: {traceback.format_exc()}") - # traceback.print_exc() finally: self.ap.logger.debug(f"Query {query} processed") diff --git a/pkg/pipeline/entities.py b/pkg/pipeline/entities.py index e8cfc427..cbeb3d0e 100644 --- a/pkg/pipeline/entities.py +++ b/pkg/pipeline/entities.py @@ -4,8 +4,7 @@ import typing import pydantic -import mirai -import mirai.models.message as mirai_message +from ..platform.types import message as platform_message from ..core import entities @@ -25,13 +24,9 @@ class StageProcessResult(pydantic.BaseModel): new_query: entities.Query - user_notice: typing.Optional[typing.Union[str, list[mirai_message.MessageComponent], mirai.MessageChain, None]] = [] + user_notice: typing.Optional[typing.Union[str, list[platform_message.MessageComponent], platform_message.MessageChain, None]] = [] """只要设置了就会发送给用户""" - # TODO delete - # admin_notice: typing.Optional[typing.Union[str, list[mirai_message.MessageComponent], mirai.MessageChain, None]] = [] - """只要设置了就会发送给管理员""" - console_notice: typing.Optional[str] = '' """只要设置了就会输出到控制台""" diff --git a/pkg/pipeline/longtext/longtext.py b/pkg/pipeline/longtext/longtext.py index 0ab34abc..ecb745d0 100644 --- a/pkg/pipeline/longtext/longtext.py +++ b/pkg/pipeline/longtext/longtext.py @@ -3,7 +3,6 @@ import traceback from PIL import Image, ImageDraw, ImageFont -from mirai.models.message import MessageComponent, Plain, MessageChain from ...core import app from . import strategy @@ -11,6 +10,7 @@ from .. import stage, entities, stagemgr from ...core import entities as core_entities from ...config import manager as cfg_mgr +from ...platform.types import message as platform_message @stage.stage_class("LongTextProcessStage") @@ -63,14 +63,14 @@ async def process(self, query: core_entities.Query, stage_inst_name: str) -> ent contains_non_plain = False for msg in query.resp_message_chain[-1]: - if not isinstance(msg, Plain): + if not isinstance(msg, platform_message.Plain): contains_non_plain = True break if contains_non_plain: self.ap.logger.debug("消息中包含非 Plain 组件,跳过长消息处理。") elif len(str(query.resp_message_chain[-1])) > self.ap.platform_cfg.data['long-text-process']['threshold']: - query.resp_message_chain[-1] = MessageChain(await self.strategy_impl.process(str(query.resp_message_chain[-1]), query)) + query.resp_message_chain[-1] = platform_message.MessageChain(await self.strategy_impl.process(str(query.resp_message_chain[-1]), query)) return entities.StageProcessResult( result_type=entities.ResultType.CONTINUE, diff --git a/pkg/pipeline/longtext/strategies/forward.py b/pkg/pipeline/longtext/strategies/forward.py index 4a790313..c39c9208 100644 --- a/pkg/pipeline/longtext/strategies/forward.py +++ b/pkg/pipeline/longtext/strategies/forward.py @@ -2,15 +2,14 @@ from __future__ import annotations import typing -from mirai.models import MessageChain -from mirai.models.message import MessageComponent, ForwardMessageNode -from mirai.models.base import MiraiBaseModel +import pydantic from .. import strategy as strategy_model from ....core import entities as core_entities +from ....platform.types import message as platform_message -class ForwardMessageDiaplay(MiraiBaseModel): +class ForwardMessageDiaplay(pydantic.BaseModel): title: str = "群聊的聊天记录" brief: str = "[聊天记录]" source: str = "聊天记录" @@ -18,13 +17,13 @@ class ForwardMessageDiaplay(MiraiBaseModel): summary: str = "查看x条转发消息" -class Forward(MessageComponent): +class Forward(platform_message.MessageComponent): """合并转发。""" type: str = "Forward" """消息组件类型。""" display: ForwardMessageDiaplay """显示信息""" - node_list: typing.List[ForwardMessageNode] + node_list: typing.List[platform_message.ForwardMessageNode] """转发消息节点列表。""" def __init__(self, *args, **kwargs): if len(args) == 1: @@ -39,7 +38,7 @@ def __str__(self): @strategy_model.strategy_class("forward") class ForwardComponentStrategy(strategy_model.LongTextStrategy): - async def process(self, message: str, query: core_entities.Query) -> list[MessageComponent]: + async def process(self, message: str, query: core_entities.Query) -> list[platform_message.MessageComponent]: display = ForwardMessageDiaplay( title="群聊的聊天记录", brief="[聊天记录]", @@ -49,10 +48,10 @@ async def process(self, message: str, query: core_entities.Query) -> list[Messag ) node_list = [ - ForwardMessageNode( + platform_message.ForwardMessageNode( sender_id=query.adapter.bot_account_id, sender_name='QQ用户', - message_chain=MessageChain([message]) + message_chain=platform_message.MessageChain([message]) ) ] diff --git a/pkg/pipeline/longtext/strategies/image.py b/pkg/pipeline/longtext/strategies/image.py index f96f03c5..9e32e598 100644 --- a/pkg/pipeline/longtext/strategies/image.py +++ b/pkg/pipeline/longtext/strategies/image.py @@ -8,8 +8,7 @@ from PIL import Image, ImageDraw, ImageFont -from mirai.models import MessageChain, Image as ImageComponent -from mirai.models.message import MessageComponent +from ....platform.types import message as platform_message from .. import strategy as strategy_model from ....core import entities as core_entities @@ -23,7 +22,7 @@ class Text2ImageStrategy(strategy_model.LongTextStrategy): async def initialize(self): self.text_render_font = ImageFont.truetype(self.ap.platform_cfg.data['long-text-process']['font-path'], 32, encoding="utf-8") - async def process(self, message: str, query: core_entities.Query) -> list[MessageComponent]: + async def process(self, message: str, query: core_entities.Query) -> list[platform_message.MessageComponent]: img_path = self.text_to_image( text_str=message, save_as='temp/{}.png'.format(int(time.time())) @@ -46,7 +45,7 @@ async def process(self, message: str, query: core_entities.Query) -> list[Messag os.remove(compressed_path) return [ - ImageComponent( + platform_message.Image( base64=b64.decode('utf-8'), ) ] diff --git a/pkg/pipeline/longtext/strategy.py b/pkg/pipeline/longtext/strategy.py index 5d7e24fb..6f66bbff 100644 --- a/pkg/pipeline/longtext/strategy.py +++ b/pkg/pipeline/longtext/strategy.py @@ -2,11 +2,10 @@ import abc import typing -import mirai -from mirai.models.message import MessageComponent from ...core import app from ...core import entities as core_entities +from ...platform.types import message as platform_message preregistered_strategies: list[typing.Type[LongTextStrategy]] = [] @@ -51,7 +50,7 @@ async def initialize(self): pass @abc.abstractmethod - async def process(self, message: str, query: core_entities.Query) -> list[MessageComponent]: + async def process(self, message: str, query: core_entities.Query) -> list[platform_message.MessageComponent]: """处理长文本 在 platform.json 中配置 long-text-process 字段,只要 文本长度超过了 threshold 就会调用此方法 @@ -61,6 +60,6 @@ async def process(self, message: str, query: core_entities.Query) -> list[Messag query (core_entities.Query): 此次请求的上下文对象 Returns: - list[mirai.models.messages.MessageComponent]: 转换后的 YiriMirai 消息组件列表 + list[platform_message.MessageComponent]: 转换后的 平台 消息组件列表 """ return [] diff --git a/pkg/pipeline/pool.py b/pkg/pipeline/pool.py index ba7f9991..45f16e66 100644 --- a/pkg/pipeline/pool.py +++ b/pkg/pipeline/pool.py @@ -2,10 +2,11 @@ import asyncio -import mirai from ..core import entities from ..platform import adapter as msadapter +from ..platform.types import message as platform_message +from ..platform.types import events as platform_events class QueryPool: @@ -30,8 +31,8 @@ async def add_query( launcher_type: entities.LauncherTypes, launcher_id: int, sender_id: int, - message_event: mirai.MessageEvent, - message_chain: mirai.MessageChain, + message_event: platform_events.MessageEvent, + message_chain: platform_message.MessageChain, adapter: msadapter.MessageSourceAdapter ) -> entities.Query: async with self.condition: diff --git a/pkg/pipeline/preproc/preproc.py b/pkg/pipeline/preproc/preproc.py index ebe4d311..3a71a841 100644 --- a/pkg/pipeline/preproc/preproc.py +++ b/pkg/pipeline/preproc/preproc.py @@ -1,11 +1,11 @@ from __future__ import annotations -import mirai from .. import stage, entities, stagemgr from ...core import entities as core_entities from ...provider import entities as llm_entities from ...plugin import events +from ...platform.types import message as platform_message @stage.stage_class("PreProcessor") @@ -55,11 +55,11 @@ async def process( content_list = [] for me in query.message_chain: - if isinstance(me, mirai.Plain): + if isinstance(me, platform_message.Plain): content_list.append( llm_entities.ContentElement.from_text(me.text) ) - elif isinstance(me, mirai.Image): + elif isinstance(me, platform_message.Image): if self.ap.provider_cfg.data['enable-vision'] and query.use_model.vision_supported: if me.url is not None: content_list.append( diff --git a/pkg/pipeline/process/handlers/chat.py b/pkg/pipeline/process/handlers/chat.py index cb8899bd..6e192b78 100644 --- a/pkg/pipeline/process/handlers/chat.py +++ b/pkg/pipeline/process/handlers/chat.py @@ -5,7 +5,6 @@ import traceback import json -import mirai from .. import handler from ... import entities @@ -13,6 +12,8 @@ from ....provider import entities as llm_entities, runnermgr from ....plugin import events +from ....platform.types import message as platform_message + class ChatMessageHandler(handler.MessageHandler): @@ -40,7 +41,7 @@ async def handle( if event_ctx.is_prevented_default(): if event_ctx.event.reply is not None: - mc = mirai.MessageChain(event_ctx.event.reply) + mc = platform_message.MessageChain(event_ctx.event.reply) query.resp_messages.append(mc) diff --git a/pkg/pipeline/process/handlers/command.py b/pkg/pipeline/process/handlers/command.py index 8c8fb8ba..cec64a45 100644 --- a/pkg/pipeline/process/handlers/command.py +++ b/pkg/pipeline/process/handlers/command.py @@ -1,13 +1,13 @@ from __future__ import annotations import typing -import mirai from .. import handler from ... import entities from ....core import entities as core_entities from ....provider import entities as llm_entities from ....plugin import events +from ....platform.types import message as platform_message class CommandHandler(handler.MessageHandler): @@ -46,7 +46,7 @@ async def handle( if event_ctx.is_prevented_default(): if event_ctx.event.reply is not None: - mc = mirai.MessageChain(event_ctx.event.reply) + mc = platform_message.MessageChain(event_ctx.event.reply) query.resp_messages.append(mc) @@ -63,8 +63,8 @@ async def handle( else: if event_ctx.event.alter is not None: - query.message_chain = mirai.MessageChain([ - mirai.Plain(event_ctx.event.alter) + query.message_chain = platform_message.MessageChain([ + platform_message.Plain(event_ctx.event.alter) ]) session = await self.ap.sess_mgr.get_session(query) diff --git a/pkg/pipeline/respback/respback.py b/pkg/pipeline/respback/respback.py index d3af14e5..08b335d5 100644 --- a/pkg/pipeline/respback/respback.py +++ b/pkg/pipeline/respback/respback.py @@ -3,7 +3,6 @@ import random import asyncio -import mirai from ...core import app @@ -20,7 +19,10 @@ class SendResponseBackStage(stage.PipelineStage): async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult: """处理 """ - random_delay = random.uniform(*self.ap.platform_cfg.data['force-delay']) + + random_range = (self.ap.platform_cfg.data['force-delay']['min'], self.ap.platform_cfg.data['force-delay']['max']) + + random_delay = random.uniform(*random_range) self.ap.logger.debug( "根据规则强制延迟回复: %s s", diff --git a/pkg/pipeline/resprule/entities.py b/pkg/pipeline/resprule/entities.py index ffee3081..22927154 100644 --- a/pkg/pipeline/resprule/entities.py +++ b/pkg/pipeline/resprule/entities.py @@ -1,9 +1,10 @@ import pydantic -import mirai + +from ...platform.types import message as platform_message class RuleJudgeResult(pydantic.BaseModel): matching: bool = False - replacement: mirai.MessageChain = None + replacement: platform_message.MessageChain = None diff --git a/pkg/pipeline/resprule/resprule.py b/pkg/pipeline/resprule/resprule.py index b7fdb372..77858f0d 100644 --- a/pkg/pipeline/resprule/resprule.py +++ b/pkg/pipeline/resprule/resprule.py @@ -1,6 +1,5 @@ from __future__ import annotations -import mirai from ...core import app from . import entities as rule_entities, rule diff --git a/pkg/pipeline/resprule/rule.py b/pkg/pipeline/resprule/rule.py index bfab4152..ad69d8a0 100644 --- a/pkg/pipeline/resprule/rule.py +++ b/pkg/pipeline/resprule/rule.py @@ -2,11 +2,11 @@ import abc import typing -import mirai - from ...core import app, entities as core_entities from . import entities +from ...platform.types import message as platform_message + preregisetered_rules: list[typing.Type[GroupRespondRule]] = [] @@ -35,7 +35,7 @@ async def initialize(self): async def match( self, message_text: str, - message_chain: mirai.MessageChain, + message_chain: platform_message.MessageChain, rule_dict: dict, query: core_entities.Query ) -> entities.RuleJudgeResult: diff --git a/pkg/pipeline/resprule/rules/atbot.py b/pkg/pipeline/resprule/rules/atbot.py index 4b39409c..a0b7a7c8 100644 --- a/pkg/pipeline/resprule/rules/atbot.py +++ b/pkg/pipeline/resprule/rules/atbot.py @@ -1,10 +1,10 @@ from __future__ import annotations -import mirai from .. import rule as rule_model from .. import entities from ....core import entities as core_entities +from ....platform.types import message as platform_message @rule_model.rule_class("at-bot") @@ -13,16 +13,16 @@ class AtBotRule(rule_model.GroupRespondRule): async def match( self, message_text: str, - message_chain: mirai.MessageChain, + message_chain: platform_message.MessageChain, rule_dict: dict, query: core_entities.Query ) -> entities.RuleJudgeResult: - if message_chain.has(mirai.At(query.adapter.bot_account_id)) and rule_dict['at']: - message_chain.remove(mirai.At(query.adapter.bot_account_id)) + if message_chain.has(platform_message.At(query.adapter.bot_account_id)) and rule_dict['at']: + message_chain.remove(platform_message.At(query.adapter.bot_account_id)) - if message_chain.has(mirai.At(query.adapter.bot_account_id)): # 回复消息时会at两次,检查并删除重复的 - message_chain.remove(mirai.At(query.adapter.bot_account_id)) + if message_chain.has(platform_message.At(query.adapter.bot_account_id)): # 回复消息时会at两次,检查并删除重复的 + message_chain.remove(platform_message.At(query.adapter.bot_account_id)) return entities.RuleJudgeResult( matching=True, diff --git a/pkg/pipeline/resprule/rules/prefix.py b/pkg/pipeline/resprule/rules/prefix.py index 98b50321..fb7bbcfc 100644 --- a/pkg/pipeline/resprule/rules/prefix.py +++ b/pkg/pipeline/resprule/rules/prefix.py @@ -1,8 +1,8 @@ -import mirai from .. import rule as rule_model from .. import entities from ....core import entities as core_entities +from ....platform.types import message as platform_message @rule_model.rule_class("prefix") @@ -11,7 +11,7 @@ class PrefixRule(rule_model.GroupRespondRule): async def match( self, message_text: str, - message_chain: mirai.MessageChain, + message_chain: platform_message.MessageChain, rule_dict: dict, query: core_entities.Query ) -> entities.RuleJudgeResult: @@ -22,7 +22,7 @@ async def match( # 查找第一个plain元素 for me in message_chain: - if isinstance(me, mirai.Plain): + if isinstance(me, platform_message.Plain): me.text = me.text[len(prefix):] return entities.RuleJudgeResult( diff --git a/pkg/pipeline/resprule/rules/random.py b/pkg/pipeline/resprule/rules/random.py index 80acf6a5..0178f2c4 100644 --- a/pkg/pipeline/resprule/rules/random.py +++ b/pkg/pipeline/resprule/rules/random.py @@ -1,10 +1,10 @@ import random -import mirai from .. import rule as rule_model from .. import entities from ....core import entities as core_entities +from ....platform.types import message as platform_message @rule_model.rule_class("random") @@ -13,7 +13,7 @@ class RandomRespRule(rule_model.GroupRespondRule): async def match( self, message_text: str, - message_chain: mirai.MessageChain, + message_chain: platform_message.MessageChain, rule_dict: dict, query: core_entities.Query ) -> entities.RuleJudgeResult: diff --git a/pkg/pipeline/resprule/rules/regexp.py b/pkg/pipeline/resprule/rules/regexp.py index aaa46449..f5f5b3f6 100644 --- a/pkg/pipeline/resprule/rules/regexp.py +++ b/pkg/pipeline/resprule/rules/regexp.py @@ -1,10 +1,10 @@ import re -import mirai from .. import rule as rule_model from .. import entities from ....core import entities as core_entities +from ....platform.types import message as platform_message @rule_model.rule_class("regexp") @@ -13,7 +13,7 @@ class RegExpRule(rule_model.GroupRespondRule): async def match( self, message_text: str, - message_chain: mirai.MessageChain, + message_chain: platform_message.MessageChain, rule_dict: dict, query: core_entities.Query ) -> entities.RuleJudgeResult: diff --git a/pkg/pipeline/wrapper/wrapper.py b/pkg/pipeline/wrapper/wrapper.py index e6ce99aa..1ffb3147 100644 --- a/pkg/pipeline/wrapper/wrapper.py +++ b/pkg/pipeline/wrapper/wrapper.py @@ -2,7 +2,6 @@ import typing -import mirai from ...core import app, entities as core_entities from .. import entities @@ -10,6 +9,7 @@ from ...core import entities as core_entities from ...config import manager as cfg_mgr from ...plugin import events +from ...platform.types import message as platform_message @stage.stage_class("ResponseWrapper") @@ -34,7 +34,7 @@ async def process( """ # 如果 resp_messages[-1] 已经是 MessageChain 了 - if isinstance(query.resp_messages[-1], mirai.MessageChain): + if isinstance(query.resp_messages[-1], platform_message.MessageChain): query.resp_message_chain.append(query.resp_messages[-1]) yield entities.StageProcessResult( @@ -45,19 +45,14 @@ async def process( else: if query.resp_messages[-1].role == 'command': - # query.resp_message_chain.append(mirai.MessageChain("[bot] "+query.resp_messages[-1].content)) - query.resp_message_chain.append(query.resp_messages[-1].get_content_mirai_message_chain(prefix_text='[bot] ')) + query.resp_message_chain.append(query.resp_messages[-1].get_content_platform_message_chain(prefix_text='[bot] ')) yield entities.StageProcessResult( result_type=entities.ResultType.CONTINUE, new_query=query ) elif query.resp_messages[-1].role == 'plugin': - # if not isinstance(query.resp_messages[-1].content, mirai.MessageChain): - # query.resp_message_chain.append(mirai.MessageChain(query.resp_messages[-1].content)) - # else: - # query.resp_message_chain.append(query.resp_messages[-1].content) - query.resp_message_chain.append(query.resp_messages[-1].get_content_mirai_message_chain()) + query.resp_message_chain.append(query.resp_messages[-1].get_content_platform_message_chain()) yield entities.StageProcessResult( result_type=entities.ResultType.CONTINUE, @@ -72,7 +67,7 @@ async def process( reply_text = '' if result.content: # 有内容 - reply_text = str(result.get_content_mirai_message_chain()) + reply_text = str(result.get_content_platform_message_chain()) # ============= 触发插件事件 =============== event_ctx = await self.ap.plugin_mgr.emit_event( @@ -96,11 +91,11 @@ async def process( else: if event_ctx.event.reply is not None: - query.resp_message_chain.append(mirai.MessageChain(event_ctx.event.reply)) + query.resp_message_chain.append(platform_message.MessageChain(event_ctx.event.reply)) else: - query.resp_message_chain.append(result.get_content_mirai_message_chain()) + query.resp_message_chain.append(result.get_content_platform_message_chain()) yield entities.StageProcessResult( result_type=entities.ResultType.CONTINUE, @@ -113,7 +108,7 @@ async def process( reply_text = f'调用函数 {".".join(function_names)}...' - query.resp_message_chain.append(mirai.MessageChain([mirai.Plain(reply_text)])) + query.resp_message_chain.append(platform_message.MessageChain([platform_message.Plain(reply_text)])) if self.ap.platform_cfg.data['track-function-calls']: @@ -139,11 +134,11 @@ async def process( else: if event_ctx.event.reply is not None: - query.resp_message_chain.append(mirai.MessageChain(event_ctx.event.reply)) + query.resp_message_chain.append(platform_message.MessageChain(event_ctx.event.reply)) else: - query.resp_message_chain.append(mirai.MessageChain([mirai.Plain(reply_text)])) + query.resp_message_chain.append(platform_message.MessageChain([platform_message.Plain(reply_text)])) yield entities.StageProcessResult( result_type=entities.ResultType.CONTINUE, diff --git a/pkg/platform/adapter.py b/pkg/platform/adapter.py index 4b159b79..7cf64a12 100644 --- a/pkg/platform/adapter.py +++ b/pkg/platform/adapter.py @@ -4,9 +4,10 @@ import typing import abc -import mirai from ..core import app +from .types import message as platform_message +from .types import events as platform_events preregistered_adapters: list[typing.Type[MessageSourceAdapter]] = [] @@ -55,28 +56,28 @@ async def send_message( self, target_type: str, target_id: str, - message: mirai.MessageChain + message: platform_message.MessageChain ): """主动发送消息 Args: target_type (str): 目标类型,`person`或`group` target_id (str): 目标ID - message (mirai.MessageChain): YiriMirai库的消息链 + message (platform.types.MessageChain): 消息链 """ raise NotImplementedError async def reply_message( self, - message_source: mirai.MessageEvent, - message: mirai.MessageChain, + message_source: platform_events.MessageEvent, + message: platform_message.MessageChain, quote_origin: bool = False ): """回复消息 Args: - message_source (mirai.MessageEvent): YiriMirai消息源事件 - message (mirai.MessageChain): YiriMirai库的消息链 + message_source (platform.types.MessageEvent): 消息源事件 + message (platform.types.MessageChain): 消息链 quote_origin (bool, optional): 是否引用原消息. Defaults to False. """ raise NotImplementedError @@ -87,27 +88,27 @@ async def is_muted(self, group_id: int) -> bool: def register_listener( self, - event_type: typing.Type[mirai.Event], - callback: typing.Callable[[mirai.Event, MessageSourceAdapter], None] + event_type: typing.Type[platform_message.Event], + callback: typing.Callable[[platform_message.Event, MessageSourceAdapter], None] ): """注册事件监听器 Args: - event_type (typing.Type[mirai.Event]): YiriMirai事件类型 - callback (typing.Callable[[mirai.Event], None]): 回调函数,接收一个参数,为YiriMirai事件 + event_type (typing.Type[platform.types.Event]): 事件类型 + callback (typing.Callable[[platform.types.Event], None]): 回调函数,接收一个参数,为事件 """ raise NotImplementedError def unregister_listener( self, - event_type: typing.Type[mirai.Event], - callback: typing.Callable[[mirai.Event, MessageSourceAdapter], None] + event_type: typing.Type[platform_message.Event], + callback: typing.Callable[[platform_message.Event, MessageSourceAdapter], None] ): """注销事件监听器 Args: - event_type (typing.Type[mirai.Event]): YiriMirai事件类型 - callback (typing.Callable[[mirai.Event], None]): 回调函数,接收一个参数,为YiriMirai事件 + event_type (typing.Type[platform.types.Event]): 事件类型 + callback (typing.Callable[[platform.types.Event], None]): 回调函数,接收一个参数,为事件 """ raise NotImplementedError @@ -127,26 +128,26 @@ async def kill(self) -> bool: class MessageConverter: """消息链转换器基类""" @staticmethod - def yiri2target(message_chain: mirai.MessageChain): - """将YiriMirai消息链转换为目标消息链 + def yiri2target(message_chain: platform_message.MessageChain): + """将源平台消息链转换为目标平台消息链 Args: - message_chain (mirai.MessageChain): YiriMirai消息链 + message_chain (platform.types.MessageChain): 源平台消息链 Returns: - typing.Any: 目标消息链 + typing.Any: 目标平台消息链 """ raise NotImplementedError @staticmethod - def target2yiri(message_chain: typing.Any) -> mirai.MessageChain: - """将目标消息链转换为YiriMirai消息链 + def target2yiri(message_chain: typing.Any) -> platform_message.MessageChain: + """将目标平台消息链转换为源平台消息链 Args: - message_chain (typing.Any): 目标消息链 + message_chain (typing.Any): 目标平台消息链 Returns: - mirai.MessageChain: YiriMirai消息链 + platform.types.MessageChain: 源平台消息链 """ raise NotImplementedError @@ -155,25 +156,25 @@ class EventConverter: """事件转换器基类""" @staticmethod - def yiri2target(event: typing.Type[mirai.Event]): - """将YiriMirai事件转换为目标事件 + def yiri2target(event: typing.Type[platform_message.Event]): + """将源平台事件转换为目标平台事件 Args: - event (typing.Type[mirai.Event]): YiriMirai事件 + event (typing.Type[platform.types.Event]): 源平台事件 Returns: - typing.Any: 目标事件 + typing.Any: 目标平台事件 """ raise NotImplementedError @staticmethod - def target2yiri(event: typing.Any) -> mirai.Event: - """将目标事件的调用参数转换为YiriMirai的事件参数对象 + def target2yiri(event: typing.Any) -> platform_message.Event: + """将目标平台事件的调用参数转换为源平台的事件参数对象 Args: - event (typing.Any): 目标事件 + event (typing.Any): 目标平台事件 Returns: - typing.Type[mirai.Event]: YiriMirai事件 + typing.Type[platform.types.Event]: 源平台事件 """ raise NotImplementedError diff --git a/pkg/platform/manager.py b/pkg/platform/manager.py index b9696426..6a391a1f 100644 --- a/pkg/platform/manager.py +++ b/pkg/platform/manager.py @@ -2,17 +2,24 @@ import json import os +import sys import logging import asyncio import traceback -from mirai import At, GroupMessage, MessageEvent, StrangerMessage, \ - FriendMessage, Image, MessageChain, Plain -import mirai +# FriendMessage, Image, MessageChain, Plain from ..platform import adapter as msadapter from ..core import app, entities as core_entities from ..plugin import events +from .types import message as platform_message +from .types import events as platform_events +from .types import entities as platform_entities + +# 处理 3.4 移除了 YiriMirai 之后,插件的兼容性问题 +from . import types as mirai +sys.modules['mirai'] = mirai + # 控制QQ消息输入输出的类 class PlatformManager: @@ -30,76 +37,40 @@ def __init__(self, ap: app.Application = None): async def initialize(self): - from .sources import yirimirai, nakuru, aiocqhttp, qqbotpy + from .sources import nakuru, aiocqhttp, qqbotpy - async def on_friend_message(event: FriendMessage, adapter: msadapter.MessageSourceAdapter): + async def on_friend_message(event: platform_events.FriendMessage, adapter: msadapter.MessageSourceAdapter): - event_ctx = await self.ap.plugin_mgr.emit_event( - event=events.PersonMessageReceived( - launcher_type='person', - launcher_id=event.sender.id, - sender_id=event.sender.id, - message_chain=event.message_chain, - query=None - ) + await self.ap.query_pool.add_query( + launcher_type=core_entities.LauncherTypes.PERSON, + launcher_id=event.sender.id, + sender_id=event.sender.id, + message_event=event, + message_chain=event.message_chain, + adapter=adapter ) - if not event_ctx.is_prevented_default(): - - await self.ap.query_pool.add_query( - launcher_type=core_entities.LauncherTypes.PERSON, - launcher_id=event.sender.id, - sender_id=event.sender.id, - message_event=event, - message_chain=event.message_chain, - adapter=adapter - ) - - async def on_stranger_message(event: StrangerMessage, adapter: msadapter.MessageSourceAdapter): + async def on_stranger_message(event: platform_events.StrangerMessage, adapter: msadapter.MessageSourceAdapter): - event_ctx = await self.ap.plugin_mgr.emit_event( - event=events.PersonMessageReceived( - launcher_type='person', - launcher_id=event.sender.id, - sender_id=event.sender.id, - message_chain=event.message_chain, - query=None - ) + await self.ap.query_pool.add_query( + launcher_type=core_entities.LauncherTypes.PERSON, + launcher_id=event.sender.id, + sender_id=event.sender.id, + message_event=event, + message_chain=event.message_chain, + adapter=adapter ) - if not event_ctx.is_prevented_default(): - - await self.ap.query_pool.add_query( - launcher_type=core_entities.LauncherTypes.PERSON, - launcher_id=event.sender.id, - sender_id=event.sender.id, - message_event=event, - message_chain=event.message_chain, - adapter=adapter - ) + async def on_group_message(event: platform_events.GroupMessage, adapter: msadapter.MessageSourceAdapter): - async def on_group_message(event: GroupMessage, adapter: msadapter.MessageSourceAdapter): - - event_ctx = await self.ap.plugin_mgr.emit_event( - event=events.GroupMessageReceived( - launcher_type='group', - launcher_id=event.group.id, - sender_id=event.sender.id, - message_chain=event.message_chain, - query=None - ) + await self.ap.query_pool.add_query( + launcher_type=core_entities.LauncherTypes.GROUP, + launcher_id=event.group.id, + sender_id=event.sender.id, + message_event=event, + message_chain=event.message_chain, + adapter=adapter ) - - if not event_ctx.is_prevented_default(): - - await self.ap.query_pool.add_query( - launcher_type=core_entities.LauncherTypes.GROUP, - launcher_id=event.group.id, - sender_id=event.sender.id, - message_event=event, - message_chain=event.message_chain, - adapter=adapter - ) index = 0 @@ -127,16 +98,16 @@ async def on_group_message(event: GroupMessage, adapter: msadapter.MessageSource if adapter_name == 'yiri-mirai': adapter_inst.register_listener( - StrangerMessage, + platform_events.StrangerMessage, on_stranger_message ) adapter_inst.register_listener( - FriendMessage, + platform_events.FriendMessage, on_friend_message ) adapter_inst.register_listener( - GroupMessage, + platform_events.GroupMessage, on_group_message ) @@ -146,13 +117,13 @@ async def on_group_message(event: GroupMessage, adapter: msadapter.MessageSource if len(self.adapters) == 0: self.ap.logger.warning('未运行平台适配器,请根据文档配置并启用平台适配器。') - async def send(self, event: mirai.MessageEvent, msg: mirai.MessageChain, adapter: msadapter.MessageSourceAdapter): + async def send(self, event: platform_events.MessageEvent, msg: platform_message.MessageChain, adapter: msadapter.MessageSourceAdapter): - if self.ap.platform_cfg.data['at-sender'] and isinstance(event, GroupMessage): + if self.ap.platform_cfg.data['at-sender'] and isinstance(event, platform_events.GroupMessage): msg.insert( 0, - At( + platform_message.At( event.sender.id ) ) @@ -167,19 +138,30 @@ async def run(self): try: tasks = [] for adapter in self.adapters: - async def exception_wrapper(adapter): + async def exception_wrapper(adapter: msadapter.MessageSourceAdapter): try: await adapter.run_async() except Exception as e: + if isinstance(e, asyncio.CancelledError): + return self.ap.logger.error('平台适配器运行出错: ' + str(e)) self.ap.logger.debug(f"Traceback: {traceback.format_exc()}") tasks.append(exception_wrapper(adapter)) for task in tasks: - asyncio.create_task(task) + self.ap.task_mgr.create_task( + task, + kind="platform-adapter", + name=f"platform-adapter-{adapter.name}", + scopes=[core_entities.LifecycleControlScope.APPLICATION, core_entities.LifecycleControlScope.PLATFORM], + ) except Exception as e: self.ap.logger.error('平台适配器运行出错: ' + str(e)) self.ap.logger.debug(f"Traceback: {traceback.format_exc()}") - + + async def shutdown(self): + for adapter in self.adapters: + await adapter.kill() + self.ap.task_mgr.cancel_by_scope(core_entities.LifecycleControlScope.PLATFORM) \ No newline at end of file diff --git a/pkg/platform/sources/aiocqhttp.py b/pkg/platform/sources/aiocqhttp.py index bd1b0677..25d197e3 100644 --- a/pkg/platform/sources/aiocqhttp.py +++ b/pkg/platform/sources/aiocqhttp.py @@ -5,31 +5,32 @@ import time import datetime -import mirai -import mirai.models.message as yiri_message import aiocqhttp from .. import adapter from ...pipeline.longtext.strategies import forward from ...core import app +from ..types import message as platform_message +from ..types import events as platform_events +from ..types import entities as platform_entities class AiocqhttpMessageConverter(adapter.MessageConverter): @staticmethod - def yiri2target(message_chain: mirai.MessageChain) -> typing.Tuple[list, int, datetime.datetime]: + def yiri2target(message_chain: platform_message.MessageChain) -> typing.Tuple[list, int, datetime.datetime]: msg_list = aiocqhttp.Message() msg_id = 0 msg_time = None for msg in message_chain: - if type(msg) is mirai.Plain: + if type(msg) is platform_message.Plain: msg_list.append(aiocqhttp.MessageSegment.text(msg.text)) - elif type(msg) is yiri_message.Source: + elif type(msg) is platform_message.Source: msg_id = msg.id msg_time = msg.time - elif type(msg) is mirai.Image: + elif type(msg) is platform_message.Image: arg = '' if msg.base64: arg = msg.base64 @@ -40,13 +41,11 @@ def yiri2target(message_chain: mirai.MessageChain) -> typing.Tuple[list, int, da elif msg.path: arg = msg.path msg_list.append(aiocqhttp.MessageSegment.image(arg)) - elif type(msg) is mirai.At: + elif type(msg) is platform_message.At: msg_list.append(aiocqhttp.MessageSegment.at(msg.target)) - elif type(msg) is mirai.AtAll: + elif type(msg) is platform_message.AtAll: msg_list.append(aiocqhttp.MessageSegment.at("all")) - elif type(msg) is mirai.Face: - msg_list.append(aiocqhttp.MessageSegment.face(msg.face_id)) - elif type(msg) is mirai.Voice: + elif type(msg) is platform_message.Voice: arg = '' if msg.base64: arg = msg.base64 @@ -74,25 +73,25 @@ def target2yiri(message: str, message_id: int = -1): yiri_msg_list = [] yiri_msg_list.append( - yiri_message.Source(id=message_id, time=datetime.datetime.now()) + platform_message.Source(id=message_id, time=datetime.datetime.now()) ) for msg in message: if msg.type == "at": if msg.data["qq"] == "all": - yiri_msg_list.append(yiri_message.AtAll()) + yiri_msg_list.append(platform_message.AtAll()) else: yiri_msg_list.append( - yiri_message.At( + platform_message.At( target=msg.data["qq"], ) ) elif msg.type == "text": - yiri_msg_list.append(yiri_message.Plain(text=msg.data["text"])) + yiri_msg_list.append(platform_message.Plain(text=msg.data["text"])) elif msg.type == "image": - yiri_msg_list.append(yiri_message.Image(url=msg.data["url"])) + yiri_msg_list.append(platform_message.Image(url=msg.data["url"])) - chain = mirai.MessageChain(yiri_msg_list) + chain = platform_message.MessageChain(yiri_msg_list) return chain @@ -100,11 +99,11 @@ def target2yiri(message: str, message_id: int = -1): class AiocqhttpEventConverter(adapter.EventConverter): @staticmethod - def yiri2target(event: mirai.Event, bot_account_id: int): + def yiri2target(event: platform_events.Event, bot_account_id: int): msg, msg_id, msg_time = AiocqhttpMessageConverter.yiri2target(event.message_chain) - if type(event) is mirai.GroupMessage: + if type(event) is platform_events.GroupMessage: role = "member" if event.sender.permission == "ADMINISTRATOR": @@ -140,7 +139,7 @@ def yiri2target(event: mirai.Event, bot_account_id: int): } return aiocqhttp.Event.from_payload(payload) - elif type(event) is mirai.FriendMessage: + elif type(event) is platform_events.FriendMessage: payload = { "post_type": "message", @@ -178,15 +177,15 @@ def target2yiri(event: aiocqhttp.Event): permission = "ADMINISTRATOR" elif event.sender["role"] == "owner": permission = "OWNER" - converted_event = mirai.GroupMessage( - sender=mirai.models.entities.GroupMember( + converted_event = platform_events.GroupMessage( + sender=platform_entities.GroupMember( id=event.sender["user_id"], # message_seq 放哪? member_name=event.sender["nickname"], permission=permission, - group=mirai.models.entities.Group( + group=platform_entities.Group( id=event.group_id, name=event.sender["nickname"], - permission=mirai.models.entities.Permission.Member, + permission=platform_entities.Permission.Member, ), special_title=event.sender["title"] if "title" in event.sender else "", join_timestamp=0, @@ -198,8 +197,8 @@ def target2yiri(event: aiocqhttp.Event): ) return converted_event elif event.message_type == "private": - return mirai.FriendMessage( - sender=mirai.models.entities.Friend( + return platform_events.FriendMessage( + sender=platform_entities.Friend( id=event.sender["user_id"], nickname=event.sender["nickname"], remark="", @@ -241,7 +240,7 @@ async def shutdown_trigger_placeholder(): self.bot = aiocqhttp.CQHttp() async def send_message( - self, target_type: str, target_id: str, message: mirai.MessageChain + self, target_type: str, target_id: str, message: platform_message.MessageChain ): aiocq_msg = AiocqhttpMessageConverter.yiri2target(message)[0] @@ -252,8 +251,8 @@ async def send_message( async def reply_message( self, - message_source: mirai.MessageEvent, - message: mirai.MessageChain, + message_source: platform_events.MessageEvent, + message: platform_message.MessageChain, quote_origin: bool = False, ): aiocq_event = AiocqhttpEventConverter.yiri2target(message_source, self.bot_account_id) @@ -271,8 +270,8 @@ async def is_muted(self, group_id: int) -> bool: def register_listener( self, - event_type: typing.Type[mirai.Event], - callback: typing.Callable[[mirai.Event, adapter.MessageSourceAdapter], None], + event_type: typing.Type[platform_events.Event], + callback: typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None], ): async def on_message(event: aiocqhttp.Event): self.bot_account_id = event.self_id @@ -281,15 +280,15 @@ async def on_message(event: aiocqhttp.Event): except: traceback.print_exc() - if event_type == mirai.GroupMessage: + if event_type == platform_events.GroupMessage: self.bot.on_message("group")(on_message) - elif event_type == mirai.FriendMessage: + elif event_type == platform_events.FriendMessage: self.bot.on_message("private")(on_message) def unregister_listener( self, - event_type: typing.Type[mirai.Event], - callback: typing.Callable[[mirai.Event, adapter.MessageSourceAdapter], None], + event_type: typing.Type[platform_events.Event], + callback: typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None], ): return super().unregister_listener(event_type, callback) diff --git a/pkg/platform/sources/nakuru.py b/pkg/platform/sources/nakuru.py index 94c29812..94993dc7 100644 --- a/pkg/platform/sources/nakuru.py +++ b/pkg/platform/sources/nakuru.py @@ -6,26 +6,28 @@ import traceback import logging -import mirai import nakuru import nakuru.entities.components as nkc from .. import adapter as adapter_model from ...pipeline.longtext.strategies import forward +from ...platform.types import message as platform_message +from ...platform.types import entities as platform_entities +from ...platform.types import events as platform_events class NakuruProjectMessageConverter(adapter_model.MessageConverter): """消息转换器""" @staticmethod - def yiri2target(message_chain: mirai.MessageChain) -> list: + def yiri2target(message_chain: platform_message.MessageChain) -> list: msg_list = [] - if type(message_chain) is mirai.MessageChain: + if type(message_chain) is platform_message.MessageChain: msg_list = message_chain.__root__ elif type(message_chain) is list: msg_list = message_chain elif type(message_chain) is str: - msg_list = [mirai.Plain(message_chain)] + msg_list = [platform_message.Plain(message_chain)] else: raise Exception("Unknown message type: " + str(message_chain) + str(type(message_chain))) @@ -33,22 +35,20 @@ def yiri2target(message_chain: mirai.MessageChain) -> list: # 遍历并转换 for component in msg_list: - if type(component) is mirai.Plain: + if type(component) is platform_message.Plain: nakuru_msg_list.append(nkc.Plain(component.text, False)) - elif type(component) is mirai.Image: + elif type(component) is platform_message.Image: if component.url is not None: nakuru_msg_list.append(nkc.Image.fromURL(component.url)) elif component.base64 is not None: nakuru_msg_list.append(nkc.Image.fromBase64(component.base64)) elif component.path is not None: nakuru_msg_list.append(nkc.Image.fromFileSystem(component.path)) - elif type(component) is mirai.Face: - nakuru_msg_list.append(nkc.Face(id=component.face_id)) - elif type(component) is mirai.At: + elif type(component) is platform_message.At: nakuru_msg_list.append(nkc.At(qq=component.target)) - elif type(component) is mirai.AtAll: + elif type(component) is platform_message.AtAll: nakuru_msg_list.append(nkc.AtAll()) - elif type(component) is mirai.Voice: + elif type(component) is platform_message.Voice: if component.url is not None: nakuru_msg_list.append(nkc.Record.fromURL(component.url)) elif component.path is not None: @@ -80,49 +80,47 @@ def yiri2target(message_chain: mirai.MessageChain) -> list: return nakuru_msg_list @staticmethod - def target2yiri(message_chain: typing.Any, message_id: int = -1) -> mirai.MessageChain: + def target2yiri(message_chain: typing.Any, message_id: int = -1) -> platform_message.MessageChain: """将Yiri的消息链转换为YiriMirai的消息链""" assert type(message_chain) is list yiri_msg_list = [] import datetime # 添加Source组件以标记message_id等信息 - yiri_msg_list.append(mirai.models.message.Source(id=message_id, time=datetime.datetime.now())) + yiri_msg_list.append(platform_message.Source(id=message_id, time=datetime.datetime.now())) for component in message_chain: if type(component) is nkc.Plain: - yiri_msg_list.append(mirai.Plain(text=component.text)) + yiri_msg_list.append(platform_message.Plain(text=component.text)) elif type(component) is nkc.Image: - yiri_msg_list.append(mirai.Image(url=component.url)) - elif type(component) is nkc.Face: - yiri_msg_list.append(mirai.Face(face_id=component.id)) + yiri_msg_list.append(platform_message.Image(url=component.url)) elif type(component) is nkc.At: - yiri_msg_list.append(mirai.At(target=component.qq)) + yiri_msg_list.append(platform_message.At(target=component.qq)) elif type(component) is nkc.AtAll: - yiri_msg_list.append(mirai.AtAll()) + yiri_msg_list.append(platform_message.AtAll()) else: pass # logging.debug("转换后的消息链: " + str(yiri_msg_list)) - chain = mirai.MessageChain(yiri_msg_list) + chain = platform_message.MessageChain(yiri_msg_list) return chain class NakuruProjectEventConverter(adapter_model.EventConverter): """事件转换器""" @staticmethod - def yiri2target(event: typing.Type[mirai.Event]): - if event is mirai.GroupMessage: + def yiri2target(event: typing.Type[platform_events.Event]): + if event is platform_events.GroupMessage: return nakuru.GroupMessage - elif event is mirai.FriendMessage: + elif event is platform_events.FriendMessage: return nakuru.FriendMessage else: raise Exception("未支持转换的事件类型: " + str(event)) @staticmethod - def target2yiri(event: typing.Any) -> mirai.Event: + def target2yiri(event: typing.Any) -> platform_events.Event: yiri_chain = NakuruProjectMessageConverter.target2yiri(event.message, event.message_id) if type(event) is nakuru.FriendMessage: # 私聊消息事件 - return mirai.FriendMessage( - sender=mirai.models.entities.Friend( + return platform_events.FriendMessage( + sender=platform_entities.Friend( id=event.sender.user_id, nickname=event.sender.nickname, remark=event.sender.nickname @@ -138,16 +136,15 @@ def target2yiri(event: typing.Any) -> mirai.Event: elif event.sender.role == "owner": permission = "OWNER" - import mirai.models.entities as entities - return mirai.GroupMessage( - sender=mirai.models.entities.GroupMember( + return platform_events.GroupMessage( + sender=platform_entities.GroupMember( id=event.sender.user_id, member_name=event.sender.nickname, permission=permission, - group=mirai.models.entities.Group( + group=platform_entities.Group( id=event.group_id, name=event.sender.nickname, - permission=entities.Permission.Member + permission=platform_entities.Permission.Member ), special_title=event.sender.title, join_timestamp=0, @@ -189,7 +186,7 @@ async def send_message( self, target_type: str, target_id: str, - message: typing.Union[mirai.MessageChain, list], + message: typing.Union[platform_message.MessageChain, list], converted: bool = False ): task = None @@ -222,8 +219,8 @@ async def send_message( async def reply_message( self, - message_source: mirai.MessageEvent, - message: mirai.MessageChain, + message_source: platform_events.MessageEvent, + message: platform_message.MessageChain, quote_origin: bool = False ): message = self.message_converter.yiri2target(message) @@ -233,14 +230,14 @@ async def reply_message( id=message_source.message_chain.message_id, ) ) - if type(message_source) is mirai.GroupMessage: + if type(message_source) is platform_events.GroupMessage: await self.send_message( "group", message_source.sender.group.id, message, converted=True ) - elif type(message_source) is mirai.FriendMessage: + elif type(message_source) is platform_events.FriendMessage: await self.send_message( "person", message_source.sender.id, @@ -258,8 +255,8 @@ def is_muted(self, group_id: int) -> bool: def register_listener( self, - event_type: typing.Type[mirai.Event], - callback: typing.Callable[[mirai.Event, adapter_model.MessageSourceAdapter], None] + event_type: typing.Type[platform_events.Event], + callback: typing.Callable[[platform_events.Event, adapter_model.MessageSourceAdapter], None] ): try: @@ -286,8 +283,8 @@ async def listener_wrapper(app: nakuru.CQHTTP, source: source_cls): def unregister_listener( self, - event_type: typing.Type[mirai.Event], - callback: typing.Callable[[mirai.Event, adapter_model.MessageSourceAdapter], None] + event_type: typing.Type[platform_events.Event], + callback: typing.Callable[[platform_events.Event, adapter_model.MessageSourceAdapter], None] ): nakuru_event_name = self.event_converter.yiri2target(event_type).__name__ @@ -331,5 +328,5 @@ async def run_async(self): while True: await asyncio.sleep(1) - def kill(self) -> bool: + async def kill(self) -> bool: return False \ No newline at end of file diff --git a/pkg/platform/sources/qqbotpy.py b/pkg/platform/sources/qqbotpy.py index a79013c3..b10be34b 100644 --- a/pkg/platform/sources/qqbotpy.py +++ b/pkg/platform/sources/qqbotpy.py @@ -6,7 +6,6 @@ import re import traceback -import mirai import botpy import botpy.message as botpy_message import botpy.types.message as botpy_message_type @@ -17,17 +16,20 @@ from ...pipeline.longtext.strategies import forward from ...core import app from ...config import manager as cfg_mgr +from ...platform.types import entities as platform_entities +from ...platform.types import events as platform_events +from ...platform.types import message as platform_message -class OfficialGroupMessage(mirai.GroupMessage): +class OfficialGroupMessage(platform_events.GroupMessage): pass -class OfficialFriendMessage(mirai.FriendMessage): +class OfficialFriendMessage(platform_events.FriendMessage): pass event_handler_mapping = { - mirai.GroupMessage: ["on_at_message_create", "on_group_at_message_create"], - mirai.FriendMessage: ["on_direct_message_create", "on_c2c_message_create"], + platform_events.GroupMessage: ["on_at_message_create", "on_group_at_message_create"], + platform_events.FriendMessage: ["on_direct_message_create", "on_c2c_message_create"], } @@ -123,16 +125,16 @@ class OfficialMessageConverter(adapter_model.MessageConverter): """QQ 官方消息转换器""" @staticmethod - def yiri2target(message_chain: mirai.MessageChain): + def yiri2target(message_chain: platform_message.MessageChain): """将 YiriMirai 的消息链转换为 QQ 官方消息""" msg_list = [] - if type(message_chain) is mirai.MessageChain: + if type(message_chain) is platform_message.MessageChain: msg_list = message_chain.__root__ elif type(message_chain) is list: msg_list = message_chain elif type(message_chain) is str: - msg_list = [mirai.Plain(text=message_chain)] + msg_list = [platform_message.Plain(text=message_chain)] else: raise Exception( "Unknown message type: " + str(message_chain) + str(type(message_chain)) @@ -153,22 +155,22 @@ def yiri2target(message_chain: mirai.MessageChain): # 遍历并转换 for component in msg_list: - if type(component) is mirai.Plain: + if type(component) is platform_message.Plain: offcial_messages.append({"type": "text", "content": component.text}) - elif type(component) is mirai.Image: + elif type(component) is platform_message.Image: if component.url is not None: offcial_messages.append({"type": "image", "content": component.url}) elif component.path is not None: offcial_messages.append( {"type": "file_image", "content": component.path} ) - elif type(component) is mirai.At: + elif type(component) is platform_message.At: offcial_messages.append({"type": "at", "content": ""}) - elif type(component) is mirai.AtAll: + elif type(component) is platform_message.AtAll: print( "上层组件要求发送 AtAll 消息,但 QQ 官方 API 不支持此消息类型,忽略此消息。" ) - elif type(component) is mirai.Voice: + elif type(component) is platform_message.Voice: print( "上层组件要求发送 Voice 消息,但 QQ 官方 API 不支持此消息类型,忽略此消息。" ) @@ -197,29 +199,29 @@ def extract_message_chain_from_obj( message: typing.Union[botpy_message.Message, botpy_message.DirectMessage, botpy_message.GroupMessage, botpy_message.C2CMessage], message_id: str = None, bot_account_id: int = 0, - ) -> mirai.MessageChain: + ) -> platform_message.MessageChain: yiri_msg_list = [] # 存id yiri_msg_list.append( - mirai.models.message.Source( + platform_message.Source( id=save_msg_id(message_id), time=datetime.datetime.now() ) ) if type(message) not in [botpy_message.DirectMessage, botpy_message.C2CMessage]: - yiri_msg_list.append(mirai.At(target=bot_account_id)) + yiri_msg_list.append(platform_message.At(target=bot_account_id)) if hasattr(message, "mentions"): for mention in message.mentions: if mention.bot: continue - yiri_msg_list.append(mirai.At(target=mention.id)) + yiri_msg_list.append(platform_message.At(target=mention.id)) for attachment in message.attachments: if attachment.content_type.startswith("image"): - yiri_msg_list.append(mirai.Image(url=attachment.url)) + yiri_msg_list.append(platform_message.Image(url=attachment.url)) else: logging.warning( "不支持的附件类型:" + attachment.content_type + ",忽略此附件。" @@ -227,9 +229,9 @@ def extract_message_chain_from_obj( content = re.sub(r"<@!\d+>", "", str(message.content)) if content.strip() != "": - yiri_msg_list.append(mirai.Plain(text=content)) + yiri_msg_list.append(platform_message.Plain(text=content)) - chain = mirai.MessageChain(yiri_msg_list) + chain = platform_message.MessageChain(yiri_msg_list) return chain @@ -244,10 +246,10 @@ def __init__(self, member_openid_mapping: OpenIDMapping[str, int], group_openid_ self.member_openid_mapping = member_openid_mapping self.group_openid_mapping = group_openid_mapping - def yiri2target(self, event: typing.Type[mirai.Event]): - if event == mirai.GroupMessage: + def yiri2target(self, event: typing.Type[platform_events.Event]): + if event == platform_events.GroupMessage: return botpy_message.Message - elif event == mirai.FriendMessage: + elif event == platform_events.FriendMessage: return botpy_message.DirectMessage else: raise Exception( @@ -257,8 +259,7 @@ def yiri2target(self, event: typing.Type[mirai.Event]): def target2yiri( self, event: typing.Union[botpy_message.Message, botpy_message.DirectMessage, botpy_message.GroupMessage, botpy_message.C2CMessage], - ) -> mirai.Event: - import mirai.models.entities as mirai_entities + ) -> platform_events.Event: if type(event) == botpy_message.Message: # 频道内,转群聊事件 permission = "MEMBER" @@ -268,15 +269,15 @@ def target2yiri( elif "4" in event.member.roles: permission = "OWNER" - return mirai.GroupMessage( - sender=mirai_entities.GroupMember( + return platform_events.GroupMessage( + sender=platform_entities.GroupMember( id=event.author.id, member_name=event.author.username, permission=permission, - group=mirai_entities.Group( + group=platform_entities.Group( id=event.channel_id, name=event.author.username, - permission=mirai_entities.Permission.Member, + permission=platform_entities.Permission.Member, ), special_title="", join_timestamp=int( @@ -297,8 +298,8 @@ def target2yiri( ), ) elif type(event) == botpy_message.DirectMessage: # 频道私聊,转私聊事件 - return mirai.FriendMessage( - sender=mirai_entities.Friend( + return platform_events.FriendMessage( + sender=platform_entities.Friend( id=event.guild_id, nickname=event.author.username, remark=event.author.username, @@ -317,14 +318,14 @@ def target2yiri( replacing_member_id = self.member_openid_mapping.save_openid(event.author.member_openid) return OfficialGroupMessage( - sender=mirai_entities.GroupMember( + sender=platform_entities.GroupMember( id=replacing_member_id, member_name=replacing_member_id, permission="MEMBER", - group=mirai_entities.Group( + group=platform_entities.Group( id=self.group_openid_mapping.save_openid(event.group_openid), name=replacing_member_id, - permission=mirai_entities.Permission.Member, + permission=platform_entities.Permission.Member, ), special_title="", join_timestamp=int(0), @@ -345,7 +346,7 @@ def target2yiri( user_id_alter = self.member_openid_mapping.save_openid(event.author.user_openid) # 实测这里的user_openid与group的member_openid是一样的 return OfficialFriendMessage( - sender=mirai_entities.Friend( + sender=platform_entities.Friend( id=user_id_alter, nickname=user_id_alter, remark=user_id_alter, @@ -410,7 +411,7 @@ def __init__(self, cfg: dict, ap: app.Application): self.bot = botpy.Client(intents=intents) async def send_message( - self, target_type: str, target_id: str, message: mirai.MessageChain + self, target_type: str, target_id: str, message: platform_message.MessageChain ): message_list = self.message_converter.yiri2target(message) @@ -437,8 +438,8 @@ async def send_message( async def reply_message( self, - message_source: mirai.MessageEvent, - message: mirai.MessageChain, + message_source: platform_events.MessageEvent, + message: platform_message.MessageChain, quote_origin: bool = False, ): @@ -463,13 +464,13 @@ async def reply_message( ] ) - if type(message_source) == mirai.GroupMessage: + if type(message_source) == platform_events.GroupMessage: args["channel_id"] = str(message_source.sender.group.id) args["msg_id"] = cached_message_ids[ str(message_source.message_chain.message_id) ] await self.bot.api.post_message(**args) - elif type(message_source) == mirai.FriendMessage: + elif type(message_source) == platform_events.FriendMessage: args["guild_id"] = str(message_source.sender.id) args["msg_id"] = cached_message_ids[ str(message_source.message_chain.message_id) @@ -534,9 +535,9 @@ async def is_muted(self, group_id: int) -> bool: def register_listener( self, - event_type: typing.Type[mirai.Event], + event_type: typing.Type[platform_events.Event], callback: typing.Callable[ - [mirai.Event, adapter_model.MessageSourceAdapter], None + [platform_events.Event, adapter_model.MessageSourceAdapter], None ], ): @@ -560,9 +561,9 @@ async def wrapper( def unregister_listener( self, - event_type: typing.Type[mirai.Event], + event_type: typing.Type[platform_events.Event], callback: typing.Callable[ - [mirai.Event, adapter_model.MessageSourceAdapter], None + [platform_events.Event, adapter_model.MessageSourceAdapter], None ], ): delattr(self.bot, event_handler_mapping[event_type]) @@ -586,8 +587,12 @@ async def run_async(self): self.member_openid_mapping, self.group_openid_mapping ) + self.cfg['ret_coro'] = True + self.ap.logger.info("运行 QQ 官方适配器") - await self.bot.start(**self.cfg) + await (await self.bot.start(**self.cfg)) - def kill(self) -> bool: - return False + async def kill(self) -> bool: + if not self.bot.is_closed(): + await self.bot.close() + return True diff --git a/pkg/platform/sources/yirimirai.py b/pkg/platform/sources/yirimirai.py deleted file mode 100644 index 7768dcf0..00000000 --- a/pkg/platform/sources/yirimirai.py +++ /dev/null @@ -1,124 +0,0 @@ -import asyncio -import typing - -import mirai -import mirai.models.bus -from mirai.bot import MiraiRunner - -from .. import adapter as adapter_model -from ...core import app - - -@adapter_model.adapter_class("yiri-mirai") -class YiriMiraiAdapter(adapter_model.MessageSourceAdapter): - """YiriMirai适配器""" - bot: mirai.Mirai - - def __init__(self, config: dict, ap: app.Application): - """初始化YiriMirai的对象""" - self.ap = ap - self.config = config - if 'adapter' not in config or \ - config['adapter'] == 'WebSocketAdapter': - self.bot = mirai.Mirai( - qq=config['qq'], - adapter=mirai.WebSocketAdapter( - host=config['host'], - port=config['port'], - verify_key=config['verifyKey'] - ) - ) - elif config['adapter'] == 'HTTPAdapter': - self.bot = mirai.Mirai( - qq=config['qq'], - adapter=mirai.HTTPAdapter( - host=config['host'], - port=config['port'], - verify_key=config['verifyKey'] - ) - ) - else: - raise Exception('Unknown adapter for YiriMirai: ' + config['adapter']) - - async def send_message( - self, - target_type: str, - target_id: str, - message: mirai.MessageChain - ): - """发送消息 - - Args: - target_type (str): 目标类型,`person`或`group` - target_id (str): 目标ID - message (mirai.MessageChain): YiriMirai库的消息链 - """ - task = None - if target_type == 'person': - task = self.bot.send_friend_message(int(target_id), message) - elif target_type == 'group': - task = self.bot.send_group_message(int(target_id), message) - else: - raise Exception('Unknown target type: ' + target_type) - - await task - - async def reply_message( - self, - message_source: mirai.MessageEvent, - message: mirai.MessageChain, - quote_origin: bool = False - ): - """回复消息 - - Args: - message_source (mirai.MessageEvent): YiriMirai消息源事件 - message (mirai.MessageChain): YiriMirai库的消息链 - quote_origin (bool, optional): 是否引用原消息. Defaults to False. - """ - await self.bot.send(message_source, message, quote_origin) - - async def is_muted(self, group_id: int) -> bool: - result = await self.bot.member_info(target=group_id, member_id=self.bot.qq).get() - if result.mute_time_remaining > 0: - return True - return False - - def register_listener( - self, - event_type: typing.Type[mirai.Event], - callback: typing.Callable[[mirai.Event, adapter_model.MessageSourceAdapter], None] - ): - """注册事件监听器 - - Args: - event_type (typing.Type[mirai.Event]): YiriMirai事件类型 - callback (typing.Callable[[mirai.Event], None]): 回调函数,接收一个参数,为YiriMirai事件 - """ - async def wrapper(event: mirai.Event): - await callback(event, self) - self.bot.on(event_type)(wrapper) - - def unregister_listener( - self, - event_type: typing.Type[mirai.Event], - callback: typing.Callable[[mirai.Event, adapter_model.MessageSourceAdapter], None] - ): - """注销事件监听器 - - Args: - event_type (typing.Type[mirai.Event]): YiriMirai事件类型 - callback (typing.Callable[[mirai.Event], None]): 回调函数,接收一个参数,为YiriMirai事件 - """ - assert isinstance(self.bot, mirai.Mirai) - bus = self.bot.bus - assert isinstance(bus, mirai.models.bus.ModelEventBus) - - bus.unsubscribe(event_type, callback) - - async def run_async(self): - self.bot_account_id = self.bot.qq - return await MiraiRunner(self.bot)._run() - - async def kill(self) -> bool: - return False diff --git a/pkg/platform/types/__init__.py b/pkg/platform/types/__init__.py new file mode 100644 index 00000000..998b0fb8 --- /dev/null +++ b/pkg/platform/types/__init__.py @@ -0,0 +1,3 @@ +from .entities import * +from .events import * +from .message import * diff --git a/pkg/platform/types/base.py b/pkg/platform/types/base.py new file mode 100644 index 00000000..d3c0be49 --- /dev/null +++ b/pkg/platform/types/base.py @@ -0,0 +1,105 @@ + +from typing import Dict, List, Type + +import pydantic.main as pdm +from pydantic import BaseModel + + +class PlatformMetaclass(pdm.ModelMetaclass): + """此类是平台中使用的 pydantic 模型的元类的基类。""" + + +def to_camel(name: str) -> str: + """将下划线命名风格转换为小驼峰命名。""" + if name[:2] == '__': # 不处理双下划线开头的特殊命名。 + return name + name_parts = name.split('_') + return ''.join(name_parts[:1] + [x.title() for x in name_parts[1:]]) + + +class PlatformBaseModel(BaseModel, metaclass=PlatformMetaclass): + """模型基类。 + + 启用了三项配置: + 1. 允许解析时传入额外的值,并将额外值保存在模型中。 + 2. 允许通过别名访问字段。 + 3. 自动生成小驼峰风格的别名。 + """ + def __init__(self, *args, **kwargs): + """""" + super().__init__(*args, **kwargs) + + def __repr__(self) -> str: + return self.__class__.__name__ + '(' + ', '.join( + (f'{k}={repr(v)}' for k, v in self.__dict__.items() if v) + ) + ')' + + class Config: + extra = 'allow' + allow_population_by_field_name = True + alias_generator = to_camel + + +class PlatformIndexedMetaclass(PlatformMetaclass): + """可以通过子类名获取子类的类的元类。""" + __indexedbases__: List[Type['PlatformIndexedModel']] = [] + __indexedmodel__ = None + + def __new__(cls, name, bases, attrs, **kwargs): + new_cls = super().__new__(cls, name, bases, attrs, **kwargs) + # 第一类:PlatformIndexedModel + if name == 'PlatformIndexedModel': + cls.__indexedmodel__ = new_cls + new_cls.__indexes__ = {} + return new_cls + # 第二类:PlatformIndexedModel 的直接子类,这些是可以通过子类名获取子类的类。 + if cls.__indexedmodel__ in bases: + cls.__indexedbases__.append(new_cls) + new_cls.__indexes__ = {} + return new_cls + # 第三类:PlatformIndexedModel 的直接子类的子类,这些添加到直接子类的索引中。 + for base in cls.__indexedbases__: + if issubclass(new_cls, base): + base.__indexes__[name] = new_cls + return new_cls + + def __getitem__(cls, name): + return cls.get_subtype(name) + + +class PlatformIndexedModel(PlatformBaseModel, metaclass=PlatformIndexedMetaclass): + """可以通过子类名获取子类的类。""" + __indexes__: Dict[str, Type['PlatformIndexedModel']] + + @classmethod + def get_subtype(cls, name: str) -> Type['PlatformIndexedModel']: + """根据类名称,获取相应的子类类型。 + + Args: + name: 类名称。 + + Returns: + Type['PlatformIndexedModel']: 子类类型。 + """ + try: + type_ = cls.__indexes__.get(name) + if not (type_ and issubclass(type_, cls)): + raise ValueError(f'`{name}` 不是 `{cls.__name__}` 的子类!') + return type_ + except AttributeError as e: + raise ValueError(f'`{name}` 不是 `{cls.__name__}` 的子类!') from None + + @classmethod + def parse_subtype(cls, obj: dict) -> 'PlatformIndexedModel': + """通过字典,构造对应的模型对象。 + + Args: + obj: 一个字典,包含了模型对象的属性。 + + Returns: + PlatformIndexedModel: 构造的对象。 + """ + if cls in PlatformIndexedModel.__subclasses__(): + ModelType = cls.get_subtype(obj['type']) + return ModelType.parse_obj(obj) + return super().parse_obj(obj) diff --git a/pkg/platform/types/entities.py b/pkg/platform/types/entities.py new file mode 100644 index 00000000..8077bd15 --- /dev/null +++ b/pkg/platform/types/entities.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +""" +此模块提供实体和配置项模型。 +""" +import abc +from datetime import datetime +from enum import Enum +import typing + +import pydantic + + +class Entity(pydantic.BaseModel): + """实体,表示一个用户或群。""" + id: int + """QQ 号或群号。""" + @abc.abstractmethod + def get_avatar_url(self) -> str: + """头像图片链接。""" + + @abc.abstractmethod + def get_name(self) -> str: + """名称。""" + + +class Friend(Entity): + """好友。""" + id: int + """QQ 号。""" + nickname: typing.Optional[str] + """昵称。""" + remark: typing.Optional[str] + """备注。""" + def get_avatar_url(self) -> str: + return f'http://q4.qlogo.cn/g?b=qq&nk={self.id}&s=140' + + def get_name(self) -> str: + return self.nickname or self.remark or '' + + +class Permission(str, Enum): + """群成员身份权限。""" + Member = "MEMBER" + """成员。""" + Administrator = "ADMINISTRATOR" + """管理员。""" + Owner = "OWNER" + """群主。""" + def __repr__(self) -> str: + return repr(self.value) + + +class Group(Entity): + """群。""" + id: int + """群号。""" + name: str + """群名称。""" + permission: Permission + """Bot 在群中的权限。""" + def get_avatar_url(self) -> str: + return f'https://p.qlogo.cn/gh/{self.id}/{self.id}/' + + def get_name(self) -> str: + return self.name + + +class GroupMember(Entity): + """群成员。""" + id: int + """QQ 号。""" + member_name: str + """群成员名称。""" + permission: Permission + """Bot 在群中的权限。""" + group: Group + """群。""" + special_title: str = '' + """群头衔。""" + join_timestamp: datetime = datetime.utcfromtimestamp(0) + """加入群的时间。""" + last_speak_timestamp: datetime = datetime.utcfromtimestamp(0) + """最后一次发言的时间。""" + mute_time_remaining: int = 0 + """禁言剩余时间。""" + def get_avatar_url(self) -> str: + return f'http://q4.qlogo.cn/g?b=qq&nk={self.id}&s=140' + + def get_name(self) -> str: + return self.member_name + + +class Client(Entity): + """来自其他客户端的用户。""" + id: int + """识别 id。""" + platform: str + """来源平台。""" + def get_avatar_url(self) -> str: + raise NotImplementedError + + def get_name(self) -> str: + return self.platform + + +class Subject(pydantic.BaseModel): + """另一种实体类型表示。""" + id: int + """QQ 号或群号。""" + kind: typing.Literal['Friend', 'Group', 'Stranger'] + """类型。""" + + +class Config(pydantic.BaseModel): + """配置项类型。""" + def modify(self, **kwargs) -> 'Config': + """修改部分设置。""" + for k, v in kwargs.items(): + if k in self.__fields__: + setattr(self, k, v) + else: + raise ValueError(f'未知配置项: {k}') + return self + + +class GroupConfigModel(Config): + """群配置。""" + name: str + """群名称。""" + confess_talk: bool + """是否允许坦白说。""" + allow_member_invite: bool + """是否允许成员邀请好友入群。""" + auto_approve: bool + """是否开启自动审批入群。""" + anonymous_chat: bool + """是否开启匿名聊天。""" + announcement: str = '' + """群公告。""" + + +class MemberInfoModel(Config, GroupMember): + """群成员信息。""" diff --git a/pkg/platform/types/events.py b/pkg/platform/types/events.py new file mode 100644 index 00000000..1b008cf4 --- /dev/null +++ b/pkg/platform/types/events.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +""" +此模块提供事件模型。 +""" +from datetime import datetime +from enum import Enum +import typing + +import pydantic + +from . import entities as platform_entities +from . import message as platform_message + + +class Event(pydantic.BaseModel): + """事件基类。 + + Args: + type: 事件名。 + """ + type: str + """事件名。""" + def __repr__(self): + return self.__class__.__name__ + '(' + ', '.join( + ( + f'{k}={repr(v)}' + for k, v in self.__dict__.items() if k != 'type' and v + ) + ) + ')' + + @classmethod + def parse_subtype(cls, obj: dict) -> 'Event': + try: + return typing.cast(Event, super().parse_subtype(obj)) + except ValueError: + return Event(type=obj['type']) + + @classmethod + def get_subtype(cls, name: str) -> typing.Type['Event']: + try: + return typing.cast(typing.Type[Event], super().get_subtype(name)) + except ValueError: + return Event + + +############################### +# Bot Event +class BotEvent(Event): + """Bot 自身事件。 + + Args: + type: 事件名。 + qq: Bot 的 QQ 号。 + """ + type: str + """事件名。""" + qq: int + """Bot 的 QQ 号。""" + + +############################### +# Message Event +class MessageEvent(Event): + """消息事件。 + + Args: + type: 事件名。 + message_chain: 消息内容。 + """ + type: str + """事件名。""" + message_chain: platform_message.MessageChain + """消息内容。""" + + +class FriendMessage(MessageEvent): + """好友消息。 + + Args: + type: 事件名。 + sender: 发送消息的好友。 + message_chain: 消息内容。 + """ + type: str = 'FriendMessage' + """事件名。""" + sender: platform_entities.Friend + """发送消息的好友。""" + message_chain: platform_message.MessageChain + """消息内容。""" + + +class GroupMessage(MessageEvent): + """群消息。 + + Args: + type: 事件名。 + sender: 发送消息的群成员。 + message_chain: 消息内容。 + """ + type: str = 'GroupMessage' + """事件名。""" + sender: platform_entities.GroupMember + """发送消息的群成员。""" + message_chain: platform_message.MessageChain + """消息内容。""" + @property + def group(self) -> platform_entities.Group: + return self.sender.group + + +class StrangerMessage(MessageEvent): + """陌生人消息。 + + Args: + type: 事件名。 + sender: 发送消息的人。 + message_chain: 消息内容。 + """ + type: str = 'StrangerMessage' + """事件名。""" + sender: platform_entities.Friend + """发送消息的人。""" + message_chain: platform_message.MessageChain + """消息内容。""" diff --git a/pkg/platform/types/message.py b/pkg/platform/types/message.py new file mode 100644 index 00000000..149c19e7 --- /dev/null +++ b/pkg/platform/types/message.py @@ -0,0 +1,817 @@ +import itertools +import logging +from datetime import datetime +from enum import Enum +from pathlib import Path +import typing + +import pydantic +import pydantic.main + +from . import entities as platform_entities +from .base import PlatformBaseModel, PlatformIndexedMetaclass, PlatformIndexedModel + + +logger = logging.getLogger(__name__) + + +class MessageComponentMetaclass(PlatformIndexedMetaclass): + """消息组件元类。""" + __message_component__ = None + + def __new__(cls, name, bases, attrs, **kwargs): + new_cls = super().__new__(cls, name, bases, attrs, **kwargs) + if name == 'MessageComponent': + cls.__message_component__ = new_cls + + if not cls.__message_component__: + return new_cls + + for base in bases: + if issubclass(base, cls.__message_component__): + # 获取字段名 + if hasattr(new_cls, '__fields__'): + # 忽略 type 字段 + new_cls.__parameter_names__ = list(new_cls.__fields__)[1:] + else: + new_cls.__parameter_names__ = [] + break + + return new_cls + + +class MessageComponent(PlatformIndexedModel, metaclass=MessageComponentMetaclass): + """消息组件。""" + type: str + """消息组件类型。""" + def __str__(self): + return '' + + def __repr__(self): + return self.__class__.__name__ + '(' + ', '.join( + ( + f'{k}={repr(v)}' + for k, v in self.__dict__.items() if k != 'type' and v + ) + ) + ')' + + def __init__(self, *args, **kwargs): + # 解析参数列表,将位置参数转化为具名参数 + parameter_names = self.__parameter_names__ + if len(args) > len(parameter_names): + raise TypeError( + f'`{self.type}`需要{len(parameter_names)}个参数,但传入了{len(args)}个。' + ) + for name, value in zip(parameter_names, args): + if name in kwargs: + raise TypeError(f'在 `{self.type}` 中,具名参数 `{name}` 与位置参数重复。') + kwargs[name] = value + + super().__init__(**kwargs) + + +TMessageComponent = typing.TypeVar('TMessageComponent', bound=MessageComponent) + + +class MessageChain(PlatformBaseModel): + """消息链。 + + 一个构造消息链的例子: + ```py + message_chain = MessageChain([ + AtAll(), + Plain("Hello World!"), + ]) + ``` + + `Plain` 可以省略。 + ```py + message_chain = MessageChain([ + AtAll(), + "Hello World!", + ]) + ``` + + 在调用 API 时,参数中需要 MessageChain 的,也可以使用 `List[MessageComponent]` 代替。 + 例如,以下两种写法是等价的: + ```py + await bot.send_friend_message(12345678, [ + Plain("Hello World!") + ]) + ``` + ```py + await bot.send_friend_message(12345678, MessageChain([ + Plain("Hello World!") + ])) + ``` + + 可以使用 `in` 运算检查消息链中: + 1. 是否有某个消息组件。 + 2. 是否有某个类型的消息组件。 + + ```py + if AtAll in message_chain: + print('AtAll') + + if At(bot.qq) in message_chain: + print('At Me') + ``` + + 消息链对索引操作进行了增强。以消息组件类型为索引,获取消息链中的全部该类型的消息组件。 + ```py + plain_list = message_chain[Plain] + '[Plain("Hello World!")]' + ``` + + 可以用加号连接两个消息链。 + ```py + MessageChain(['Hello World!']) + MessageChain(['Goodbye World!']) + # 返回 MessageChain([Plain("Hello World!"), Plain("Goodbye World!")]) + ``` + + """ + __root__: typing.List[MessageComponent] + + @staticmethod + def _parse_message_chain(msg_chain: typing.Iterable): + result = [] + for msg in msg_chain: + if isinstance(msg, dict): + result.append(MessageComponent.parse_subtype(msg)) + elif isinstance(msg, MessageComponent): + result.append(msg) + elif isinstance(msg, str): + result.append(Plain(msg)) + else: + raise TypeError( + f"消息链中元素需为 dict 或 str 或 MessageComponent,当前类型:{type(msg)}" + ) + return result + + @pydantic.validator('__root__', always=True, pre=True) + def _parse_component(cls, msg_chain): + if isinstance(msg_chain, (str, MessageComponent)): + msg_chain = [msg_chain] + if not msg_chain: + msg_chain = [] + return cls._parse_message_chain(msg_chain) + + @classmethod + def parse_obj(cls, msg_chain: typing.Iterable): + """通过列表形式的消息链,构造对应的 `MessageChain` 对象。 + + Args: + msg_chain: 列表形式的消息链。 + """ + result = cls._parse_message_chain(msg_chain) + return cls(__root__=result) + + def __init__(self, __root__: typing.Iterable[MessageComponent] = None): + super().__init__(__root__=__root__) + + def __str__(self): + return "".join(str(component) for component in self.__root__) + + def __repr__(self): + return f'{self.__class__.__name__}({self.__root__!r})' + + def __iter__(self): + yield from self.__root__ + + def get_first(self, + t: typing.Type[TMessageComponent]) -> typing.Optional[TMessageComponent]: + """获取消息链中第一个符合类型的消息组件。""" + for component in self: + if isinstance(component, t): + return component + return None + + @typing.overload + def __getitem__(self, index: int) -> MessageComponent: + ... + + @typing.overload + def __getitem__(self, index: slice) -> typing.List[MessageComponent]: + ... + + @typing.overload + def __getitem__(self, + index: typing.Type[TMessageComponent]) -> typing.List[TMessageComponent]: + ... + + @typing.overload + def __getitem__( + self, index: typing.Tuple[typing.Type[TMessageComponent], int] + ) -> typing.List[TMessageComponent]: + ... + + def __getitem__( + self, index: typing.Union[int, slice, typing.Type[TMessageComponent], + typing.Tuple[typing.Type[TMessageComponent], int]] + ) -> typing.Union[MessageComponent, typing.List[MessageComponent], + typing.List[TMessageComponent]]: + return self.get(index) + + def __setitem__( + self, key: typing.Union[int, slice], + value: typing.Union[MessageComponent, str, typing.Iterable[typing.Union[MessageComponent, + str]]] + ): + if isinstance(value, str): + value = Plain(value) + if isinstance(value, typing.Iterable): + value = (Plain(c) if isinstance(c, str) else c for c in value) + self.__root__[key] = value # type: ignore + + def __delitem__(self, key: typing.Union[int, slice]): + del self.__root__[key] + + def __reversed__(self) -> typing.Iterable[MessageComponent]: + return reversed(self.__root__) + + def has( + self, sub: typing.Union[MessageComponent, typing.Type[MessageComponent], + 'MessageChain', str] + ) -> bool: + """判断消息链中: + 1. 是否有某个消息组件。 + 2. 是否有某个类型的消息组件。 + + Args: + sub (`Union[MessageComponent, Type[MessageComponent], 'MessageChain', str]`): + 若为 `MessageComponent`,则判断该组件是否在消息链中。 + 若为 `Type[MessageComponent]`,则判断该组件类型是否在消息链中。 + + Returns: + bool: 是否找到。 + """ + if isinstance(sub, type): # 检测消息链中是否有某种类型的对象 + for i in self: + if type(i) is sub: + return True + return False + if isinstance(sub, MessageComponent): # 检查消息链中是否有某个组件 + for i in self: + if i == sub: + return True + return False + raise TypeError(f"类型不匹配,当前类型:{type(sub)}") + + def __contains__(self, sub) -> bool: + return self.has(sub) + + def __ge__(self, other): + return other in self + + def __len__(self) -> int: + return len(self.__root__) + + def __add__( + self, other: typing.Union['MessageChain', MessageComponent, str] + ) -> 'MessageChain': + if isinstance(other, MessageChain): + return self.__class__(self.__root__ + other.__root__) + if isinstance(other, str): + return self.__class__(self.__root__ + [Plain(other)]) + if isinstance(other, MessageComponent): + return self.__class__(self.__root__ + [other]) + return NotImplemented + + def __radd__(self, other: typing.Union[MessageComponent, str]) -> 'MessageChain': + if isinstance(other, MessageComponent): + return self.__class__([other] + self.__root__) + if isinstance(other, str): + return self.__class__( + [typing.cast(MessageComponent, Plain(other))] + self.__root__ + ) + return NotImplemented + + def __mul__(self, other: int): + if isinstance(other, int): + return self.__class__(self.__root__ * other) + return NotImplemented + + def __rmul__(self, other: int): + return self.__mul__(other) + + def __iadd__(self, other: typing.Iterable[typing.Union[MessageComponent, str]]): + self.extend(other) + + def __imul__(self, other: int): + if isinstance(other, int): + self.__root__ *= other + return NotImplemented + + def index( + self, + x: typing.Union[MessageComponent, typing.Type[MessageComponent]], + i: int = 0, + j: int = -1 + ) -> int: + """返回 x 在消息链中首次出现项的索引号(索引号在 i 或其后且在 j 之前)。 + + Args: + x (`Union[MessageComponent, Type[MessageComponent]]`): + 要查找的消息元素或消息元素类型。 + i: 从哪个位置开始查找。 + j: 查找到哪个位置结束。 + + Returns: + int: 如果找到,则返回索引号。 + + Raises: + ValueError: 没有找到。 + TypeError: 类型不匹配。 + """ + if isinstance(x, type): + l = len(self) + if i < 0: + i += l + if i < 0: + i = 0 + if j < 0: + j += l + if j > l: + j = l + for index in range(i, j): + if type(self[index]) is x: + return index + raise ValueError("消息链中不存在该类型的组件。") + if isinstance(x, MessageComponent): + return self.__root__.index(x, i, j) + raise TypeError(f"类型不匹配,当前类型:{type(x)}") + + def count(self, x: typing.Union[MessageComponent, typing.Type[MessageComponent]]) -> int: + """返回消息链中 x 出现的次数。 + + Args: + x (`Union[MessageComponent, Type[MessageComponent]]`): + 要查找的消息元素或消息元素类型。 + + Returns: + int: 次数。 + """ + if isinstance(x, type): + return sum(1 for i in self if type(i) is x) + if isinstance(x, MessageComponent): + return self.__root__.count(x) + raise TypeError(f"类型不匹配,当前类型:{type(x)}") + + def extend(self, x: typing.Iterable[typing.Union[MessageComponent, str]]): + """将另一个消息链中的元素添加到消息链末尾。 + + Args: + x: 另一个消息链,也可为消息元素或字符串元素的序列。 + """ + self.__root__.extend(Plain(c) if isinstance(c, str) else c for c in x) + + def append(self, x: typing.Union[MessageComponent, str]): + """将一个消息元素或字符串元素添加到消息链末尾。 + + Args: + x: 消息元素或字符串元素。 + """ + self.__root__.append(Plain(x) if isinstance(x, str) else x) + + def insert(self, i: int, x: typing.Union[MessageComponent, str]): + """将一个消息元素或字符串添加到消息链中指定位置。 + + Args: + i: 插入位置。 + x: 消息元素或字符串元素。 + """ + self.__root__.insert(i, Plain(x) if isinstance(x, str) else x) + + def pop(self, i: int = -1) -> MessageComponent: + """从消息链中移除并返回指定位置的元素。 + + Args: + i: 移除位置。默认为末尾。 + + Returns: + MessageComponent: 移除的元素。 + """ + return self.__root__.pop(i) + + def remove(self, x: typing.Union[MessageComponent, typing.Type[MessageComponent]]): + """从消息链中移除指定元素或指定类型的一个元素。 + + Args: + x: 指定的元素或元素类型。 + """ + if isinstance(x, type): + self.pop(self.index(x)) + if isinstance(x, MessageComponent): + self.__root__.remove(x) + + def exclude( + self, + x: typing.Union[MessageComponent, typing.Type[MessageComponent]], + count: int = -1 + ) -> 'MessageChain': + """返回移除指定元素或指定类型的元素后剩余的消息链。 + + Args: + x: 指定的元素或元素类型。 + count: 至多移除的数量。默认为全部移除。 + + Returns: + MessageChain: 剩余的消息链。 + """ + def _exclude(): + nonlocal count + x_is_type = isinstance(x, type) + for c in self: + if count > 0 and ((x_is_type and type(c) is x) or c == x): + count -= 1 + continue + yield c + + return self.__class__(_exclude()) + + def reverse(self): + """将消息链原地翻转。""" + self.__root__.reverse() + + @classmethod + def join(cls, *args: typing.Iterable[typing.Union[str, MessageComponent]]): + return cls( + Plain(c) if isinstance(c, str) else c + for c in itertools.chain(*args) + ) + + @property + def source(self) -> typing.Optional['Source']: + """获取消息链中的 `Source` 对象。""" + return self.get_first(Source) + + @property + def message_id(self) -> int: + """获取消息链的 message_id,若无法获取,返回 -1。""" + source = self.source + return source.id if source else -1 + + +TMessage = typing.Union[MessageChain, typing.Iterable[typing.Union[MessageComponent, str]], + MessageComponent, str] +"""可以转化为 MessageChain 的类型。""" + + +class Source(MessageComponent): + """源。包含消息的基本信息。""" + type: str = "Source" + """消息组件类型。""" + id: int + """消息的识别号,用于引用回复(Source 类型永远为 MessageChain 的第一个元素)。""" + time: datetime + """消息时间。""" + + +class Plain(MessageComponent): + """纯文本。""" + type: str = "Plain" + """消息组件类型。""" + text: str + """文字消息。""" + def __str__(self): + return self.text + + def __repr__(self): + return f'Plain({self.text!r})' + + +class Quote(MessageComponent): + """引用。""" + type: str = "Quote" + """消息组件类型。""" + id: typing.Optional[int] = None + """被引用回复的原消息的 message_id。""" + group_id: typing.Optional[int] = None + """被引用回复的原消息所接收的群号,当为好友消息时为0。""" + sender_id: typing.Optional[int] = None + """被引用回复的原消息的发送者的QQ号。""" + target_id: typing.Optional[int] = None + """被引用回复的原消息的接收者者的QQ号(或群号)。""" + origin: MessageChain + """被引用回复的原消息的消息链对象。""" + + @pydantic.validator("origin", always=True, pre=True) + def origin_formater(cls, v): + return MessageChain.parse_obj(v) + + +class At(MessageComponent): + """At某人。""" + type: str = "At" + """消息组件类型。""" + target: int + """群员 QQ 号。""" + display: typing.Optional[str] = None + """At时显示的文字,发送消息时无效,自动使用群名片。""" + def __eq__(self, other): + return isinstance(other, At) and self.target == other.target + + def __str__(self): + return f"@{self.display or self.target}" + + +class AtAll(MessageComponent): + """At全体。""" + type: str = "AtAll" + """消息组件类型。""" + def __str__(self): + return "@全体成员" + + +class Image(MessageComponent): + """图片。""" + type: str = "Image" + """消息组件类型。""" + image_id: typing.Optional[str] = None + """图片的 image_id,群图片与好友图片格式不同。不为空时将忽略 url 属性。""" + url: typing.Optional[pydantic.HttpUrl] = None + """图片的 URL,发送时可作网络图片的链接;接收时为腾讯图片服务器的链接,可用于图片下载。""" + path: typing.Union[str, Path, None] = None + """图片的路径,发送本地图片。""" + base64: typing.Optional[str] = None + """图片的 Base64 编码。""" + def __eq__(self, other): + return isinstance( + other, Image + ) and self.type == other.type and self.uuid == other.uuid + + def __str__(self): + return '[图片]' + + @pydantic.validator('path') + def validate_path(cls, path: typing.Union[str, Path, None]): + """修复 path 参数的行为,使之相对于 LangBot 的启动路径。""" + if path: + try: + return str(Path(path).resolve(strict=True)) + except FileNotFoundError: + raise ValueError(f"无效路径:{path}") + else: + return path + + @property + def uuid(self): + image_id = self.image_id + if image_id[0] == '{': # 群图片 + image_id = image_id[1:37] + elif image_id[0] == '/': # 好友图片 + image_id = image_id[1:] + return image_id + + async def download( + self, + filename: typing.Union[str, Path, None] = None, + directory: typing.Union[str, Path, None] = None, + determine_type: bool = True + ): + """下载图片到本地。 + + Args: + filename: 下载到本地的文件路径。与 `directory` 二选一。 + directory: 下载到本地的文件夹路径。与 `filename` 二选一。 + determine_type: 是否自动根据图片类型确定拓展名,默认为 True。 + """ + if not self.url: + logger.warning(f'图片 `{self.uuid}` 无 url 参数,下载失败。') + return + + import httpx + async with httpx.AsyncClient() as client: + response = await client.get(self.url) + response.raise_for_status() + content = response.content + + if filename: + path = Path(filename) + if determine_type: + import imghdr + path = path.with_suffix( + '.' + str(imghdr.what(None, content)) + ) + path.parent.mkdir(parents=True, exist_ok=True) + elif directory: + import imghdr + path = Path(directory) + path.mkdir(parents=True, exist_ok=True) + path = path / f'{self.uuid}.{imghdr.what(None, content)}' + else: + raise ValueError("请指定文件路径或文件夹路径!") + + import aiofiles + async with aiofiles.open(path, 'wb') as f: + await f.write(content) + + return path + + @classmethod + async def from_local( + cls, + filename: typing.Union[str, Path, None] = None, + content: typing.Optional[bytes] = None, + ) -> "Image": + """从本地文件路径加载图片,以 base64 的形式传递。 + + Args: + filename: 从本地文件路径加载图片,与 `content` 二选一。 + content: 从本地文件内容加载图片,与 `filename` 二选一。 + + Returns: + Image: 图片对象。 + """ + if content: + pass + elif filename: + path = Path(filename) + import aiofiles + async with aiofiles.open(path, 'rb') as f: + content = await f.read() + else: + raise ValueError("请指定图片路径或图片内容!") + import base64 + img = cls(base64=base64.b64encode(content).decode()) + return img + + @classmethod + def from_unsafe_path(cls, path: typing.Union[str, Path]) -> "Image": + """从不安全的路径加载图片。 + + Args: + path: 从不安全的路径加载图片。 + + Returns: + Image: 图片对象。 + """ + return cls.construct(path=str(path)) + + +class Unknown(MessageComponent): + """未知。""" + type: str = "Unknown" + """消息组件类型。""" + text: str + """文本。""" + + +class Voice(MessageComponent): + """语音。""" + type: str = "Voice" + """消息组件类型。""" + voice_id: typing.Optional[str] = None + """语音的 voice_id,不为空时将忽略 url 属性。""" + url: typing.Optional[str] = None + """语音的 URL,发送时可作网络语音的链接;接收时为腾讯语音服务器的链接,可用于语音下载。""" + path: typing.Optional[str] = None + """语音的路径,发送本地语音。""" + base64: typing.Optional[str] = None + """语音的 Base64 编码。""" + length: typing.Optional[int] = None + """语音的长度,单位为秒。""" + @pydantic.validator('path') + def validate_path(cls, path: typing.Optional[str]): + """修复 path 参数的行为,使之相对于 LangBot 的启动路径。""" + if path: + try: + return str(Path(path).resolve(strict=True)) + except FileNotFoundError: + raise ValueError(f"无效路径:{path}") + else: + return path + + def __str__(self): + return '[语音]' + + async def download( + self, + filename: typing.Union[str, Path, None] = None, + directory: typing.Union[str, Path, None] = None + ): + """下载语音到本地。 + + 语音采用 silk v3 格式,silk 格式的编码解码请使用 [graiax-silkcoder](https://pypi.org/project/graiax-silkcoder/)。 + + Args: + filename: 下载到本地的文件路径。与 `directory` 二选一。 + directory: 下载到本地的文件夹路径。与 `filename` 二选一。 + """ + if not self.url: + logger.warning(f'语音 `{self.voice_id}` 无 url 参数,下载失败。') + return + + import httpx + async with httpx.AsyncClient() as client: + response = await client.get(self.url) + response.raise_for_status() + content = response.content + + if filename: + path = Path(filename) + path.parent.mkdir(parents=True, exist_ok=True) + elif directory: + path = Path(directory) + path.mkdir(parents=True, exist_ok=True) + path = path / f'{self.voice_id}.silk' + else: + raise ValueError("请指定文件路径或文件夹路径!") + + import aiofiles + async with aiofiles.open(path, 'wb') as f: + await f.write(content) + + @classmethod + async def from_local( + cls, + filename: typing.Union[str, Path, None] = None, + content: typing.Optional[bytes] = None, + ) -> "Voice": + """从本地文件路径加载语音,以 base64 的形式传递。 + + Args: + filename: 从本地文件路径加载语音,与 `content` 二选一。 + content: 从本地文件内容加载语音,与 `filename` 二选一。 + """ + if content: + pass + if filename: + path = Path(filename) + import aiofiles + async with aiofiles.open(path, 'rb') as f: + content = await f.read() + else: + raise ValueError("请指定语音路径或语音内容!") + import base64 + img = cls(base64=base64.b64encode(content).decode()) + return img + + +class ForwardMessageNode(pydantic.BaseModel): + """合并转发中的一条消息。""" + sender_id: typing.Optional[int] = None + """发送人QQ号。""" + sender_name: typing.Optional[str] = None + """显示名称。""" + message_chain: typing.Optional[MessageChain] = None + """消息内容。""" + message_id: typing.Optional[int] = None + """消息的 message_id,可以只使用此属性,从缓存中读取消息内容。""" + time: typing.Optional[datetime] = None + """发送时间。""" + @pydantic.validator('message_chain', check_fields=False) + def _validate_message_chain(cls, value: typing.Union[MessageChain, list]): + if isinstance(value, list): + return MessageChain.parse_obj(value) + return value + + @classmethod + def create( + cls, sender: typing.Union[platform_entities.Friend, platform_entities.GroupMember], message: MessageChain + ) -> 'ForwardMessageNode': + """从消息链生成转发消息。 + + Args: + sender: 发送人。 + message: 消息内容。 + + Returns: + ForwardMessageNode: 生成的一条消息。 + """ + return ForwardMessageNode( + sender_id=sender.id, + sender_name=sender.get_name(), + message_chain=message + ) + + +class Forward(MessageComponent): + """合并转发。""" + type: str = "Forward" + """消息组件类型。""" + node_list: typing.List[ForwardMessageNode] + """转发消息节点列表。""" + def __init__(self, *args, **kwargs): + if len(args) == 1: + self.node_list = args[0] + super().__init__(**kwargs) + super().__init__(*args, **kwargs) + + def __str__(self): + return '[聊天记录]' + + +class File(MessageComponent): + """文件。""" + type: str = "File" + """消息组件类型。""" + id: str + """文件识别 ID。""" + name: str + """文件名称。""" + size: int + """文件大小。""" + def __str__(self): + return f'[文件]{self.name}' + diff --git a/pkg/plugin/context.py b/pkg/plugin/context.py index 42cb6be2..e3d04aa4 100644 --- a/pkg/plugin/context.py +++ b/pkg/plugin/context.py @@ -3,11 +3,12 @@ import typing import abc import pydantic -import mirai +import enum from . import events from ..provider.tools import entities as tools_entities from ..core import app +from ..platform.types import message as platform_message def register( @@ -85,15 +86,24 @@ class BasePlugin(metaclass=abc.ABCMeta): """应用程序对象""" def __init__(self, host: APIHost): + """初始化阶段被调用""" self.host = host async def initialize(self): - """初始化插件""" + """初始化阶段被调用""" + pass + + async def destroy(self): + """释放/禁用插件时被调用""" + pass + + def __del__(self): + """释放/禁用插件时被调用""" pass class APIHost: - """QChatGPT API 宿主""" + """LangBot API 宿主""" ap: app.Application @@ -126,7 +136,7 @@ def require_ver( if self.ap.ver_mgr.compare_version_str(qchatgpt_version, ge) < 0 or \ (self.ap.ver_mgr.compare_version_str(qchatgpt_version, le) > 0): - raise Exception("QChatGPT 版本不满足要求,某些功能(可能是由插件提供的)无法正常使用。(要求版本:{}-{},但当前版本:{})".format(ge, le, qchatgpt_version)) + raise Exception("LangBot 版本不满足要求,某些功能(可能是由插件提供的)无法正常使用。(要求版本:{}-{},但当前版本:{})".format(ge, le, qchatgpt_version)) return True @@ -174,11 +184,11 @@ def add_return(self, key: str, ret): self.__return_value__[key] = [] self.__return_value__[key].append(ret) - async def reply(self, message_chain: mirai.MessageChain): + async def reply(self, message_chain: platform_message.MessageChain): """回复此次消息请求 Args: - message_chain (mirai.MessageChain): YiriMirai库的消息链,若用户使用的不是 YiriMirai 适配器,程序也能自动转换为目标消息链 + message_chain (platform.types.MessageChain): 源平台的消息链,若用户使用的不是源平台适配器,程序也能自动转换为目标平台消息链 """ await self.host.ap.platform_mgr.send( event=self.event.query.message_event, @@ -190,14 +200,14 @@ async def send_message( self, target_type: str, target_id: str, - message: mirai.MessageChain + message: platform_message.MessageChain ): """主动发送消息 Args: target_type (str): 目标类型,`person`或`group` target_id (str): 目标ID - message (mirai.MessageChain): YiriMirai库的消息链,若用户使用的不是 YiriMirai 适配器,程序也能自动转换为目标消息链 + message (platform.types.MessageChain): 源平台的消息链,若用户使用的不是源平台适配器,程序也能自动转换为目标平台消息链 """ await self.event.query.adapter.send_message( target_type=target_type, @@ -247,6 +257,16 @@ def __init__(self, host: APIHost, event: events.BaseEventModel): EventContext.eid += 1 +class RuntimeContainerStatus(enum.Enum): + """插件容器状态""" + + MOUNTED = "mounted" + """已加载进内存,所有位于运行时记录中的 RuntimeContainer 至少是这个状态""" + + INITIALIZED = "initialized" + """已初始化""" + + class RuntimeContainer(pydantic.BaseModel): """运行时的插件容器 @@ -294,6 +314,9 @@ class RuntimeContainer(pydantic.BaseModel): content_functions: list[tools_entities.LLMFunction] = [] """内容函数""" + status: RuntimeContainerStatus = RuntimeContainerStatus.MOUNTED + """插件状态""" + class Config: arbitrary_types_allowed = True @@ -318,5 +341,30 @@ def set_from_setting_dict( self.priority = setting['priority'] self.enabled = setting['enabled'] - for function in self.content_functions: - function.enable = self.enabled + def model_dump(self, *args, **kwargs): + return { + 'name': self.plugin_name, + 'description': self.plugin_description, + 'version': self.plugin_version, + 'author': self.plugin_author, + 'source': self.plugin_source, + 'main_file': self.main_file, + 'pkg_path': self.pkg_path, + 'enabled': self.enabled, + 'priority': self.priority, + 'event_handlers': { + event_name.__name__: handler.__name__ + for event_name, handler in self.event_handlers.items() + }, + 'content_functions': [ + { + 'name': function.name, + 'human_desc': function.human_desc, + 'description': function.description, + 'parameters': function.parameters, + 'func': function.func.__name__, + } + for function in self.content_functions + ], + 'status': self.status.value, + } diff --git a/pkg/plugin/events.py b/pkg/plugin/events.py index b5919762..013dd113 100644 --- a/pkg/plugin/events.py +++ b/pkg/plugin/events.py @@ -3,10 +3,10 @@ import typing import pydantic -import mirai from ..core import entities as core_entities from ..provider import entities as llm_entities +from ..platform.types import message as platform_message class BaseEventModel(pydantic.BaseModel): @@ -31,7 +31,7 @@ class PersonMessageReceived(BaseEventModel): sender_id: int """发送者ID(QQ号)""" - message_chain: mirai.MessageChain + message_chain: platform_message.MessageChain class GroupMessageReceived(BaseEventModel): @@ -43,7 +43,7 @@ class GroupMessageReceived(BaseEventModel): sender_id: int - message_chain: mirai.MessageChain + message_chain: platform_message.MessageChain class PersonNormalMessageReceived(BaseEventModel): diff --git a/pkg/plugin/installer.py b/pkg/plugin/installer.py index 13501c0d..b9ffab8b 100644 --- a/pkg/plugin/installer.py +++ b/pkg/plugin/installer.py @@ -3,7 +3,7 @@ import typing import abc -from ..core import app +from ..core import app, taskmgr class PluginInstaller(metaclass=abc.ABCMeta): @@ -21,6 +21,7 @@ async def initialize(self): async def install_plugin( self, plugin_source: str, + task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): """安装插件 """ @@ -30,6 +31,7 @@ async def install_plugin( async def uninstall_plugin( self, plugin_name: str, + task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): """卸载插件 """ @@ -40,6 +42,7 @@ async def update_plugin( self, plugin_name: str, plugin_source: str=None, + task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): """更新插件 """ diff --git a/pkg/plugin/installers/github.py b/pkg/plugin/installers/github.py index 93505ae5..45439579 100644 --- a/pkg/plugin/installers/github.py +++ b/pkg/plugin/installers/github.py @@ -5,10 +5,14 @@ import shutil import zipfile -import requests +import aiohttp +import aiofiles +import aiofiles.os as aiofiles_os +import aioshutil from .. import installer, errors from ...utils import pkgmgr +from ...core import taskmgr class GitHubRepoInstaller(installer.PluginInstaller): @@ -28,65 +32,65 @@ def get_github_plugin_repo_label(self, repo_url: str) -> list[str]: return repo[0].split("/") else: return None + + async def download_plugin_source_code(self, repo_url: str, target_path: str, task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder()) -> str: + """下载插件源码(全异步)""" - async def download_plugin_source_code(self, repo_url: str, target_path: str) -> str: - """下载插件源码""" - # 检查源类型 - # 提取 username/repo , 正则表达式 repo = self.get_github_plugin_repo_label(repo_url) target_path += repo[1] - if repo is not None: # github - self.ap.logger.debug("正在下载源码...") - - zipball_url = f"https://api.github.com/repos/{'/'.join(repo)}/zipball/HEAD" - - zip_resp = requests.get( - url=zipball_url, proxies=self.ap.proxy_mgr.get_forward_proxies(), stream=True - ) - - if zip_resp.status_code != 200: - raise Exception("下载源码失败: {}".format(zip_resp.text)) - - if os.path.exists("temp/" + target_path): - shutil.rmtree("temp/" + target_path) - - if os.path.exists(target_path): - shutil.rmtree(target_path) + if repo is None: + raise errors.PluginInstallerError('仅支持GitHub仓库地址') + + self.ap.logger.debug("正在下载源码...") + task_context.trace("下载源码...", "download-plugin-source-code") + + zipball_url = f"https://api.github.com/repos/{'/'.join(repo)}/zipball/HEAD" + + zip_resp: bytes = None + + async with aiohttp.ClientSession(trust_env=True) as session: + async with session.get( + url=zipball_url, + timeout=aiohttp.ClientTimeout(total=300) + ) as resp: + if resp.status != 200: + raise errors.PluginInstallerError(f"下载源码失败: {resp.text}") + + zip_resp = await resp.read() + + if await aiofiles_os.path.exists("temp/" + target_path): + await aioshutil.rmtree("temp/" + target_path) - os.makedirs("temp/" + target_path) + if await aiofiles_os.path.exists(target_path): + await aioshutil.rmtree(target_path) - with open("temp/" + target_path + "/source.zip", "wb") as f: - for chunk in zip_resp.iter_content(chunk_size=1024): - if chunk: - f.write(chunk) + await aiofiles_os.makedirs("temp/" + target_path) - self.ap.logger.debug("解压中...") + async with aiofiles.open("temp/" + target_path + "/source.zip", "wb") as f: + await f.write(zip_resp) - with zipfile.ZipFile("temp/" + target_path + "/source.zip", "r") as zip_ref: - zip_ref.extractall("temp/" + target_path) - os.remove("temp/" + target_path + "/source.zip") + self.ap.logger.debug("解压中...") + task_context.trace("解压中...", "unzip-plugin-source-code") + + with zipfile.ZipFile("temp/" + target_path + "/source.zip", "r") as zip_ref: + zip_ref.extractall("temp/" + target_path) + await aiofiles_os.remove("temp/" + target_path + "/source.zip") - # 目标是 username-repo-hash , 用正则表达式提取完整的文件夹名,复制到 plugins/repo - import glob + import glob - # 获取解压后的文件夹名 - unzip_dir = glob.glob("temp/" + target_path + "/*")[0] + unzip_dir = glob.glob("temp/" + target_path + "/*")[0] - # 复制到 plugins/repo - shutil.copytree(unzip_dir, target_path + "/") + await aioshutil.copytree(unzip_dir, target_path + "/") - # 删除解压后的文件夹 - shutil.rmtree(unzip_dir) - - self.ap.logger.debug("源码下载完成。") - else: - raise errors.PluginInstallerError('仅支持GitHub仓库地址') + await aioshutil.rmtree(unzip_dir) + + self.ap.logger.debug("源码下载完成。") return repo[1] - + async def install_requirements(self, path: str): if os.path.exists(path + "/requirements.txt"): pkgmgr.install_requirements(path + "/requirements.txt") @@ -94,13 +98,20 @@ async def install_requirements(self, path: str): async def install_plugin( self, plugin_source: str, + task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): """安装插件 """ - repo_label = await self.download_plugin_source_code(plugin_source, "plugins/") + task_context.trace("下载插件源码...", "install-plugin") + + repo_label = await self.download_plugin_source_code(plugin_source, "plugins/", task_context) + + task_context.trace("安装插件依赖...", "install-plugin") await self.install_requirements("plugins/" + repo_label) + task_context.trace("完成.", "install-plugin") + await self.ap.plugin_mgr.setting.record_installed_plugin_source( "plugins/"+repo_label+'/', plugin_source ) @@ -108,6 +119,7 @@ async def install_plugin( async def uninstall_plugin( self, plugin_name: str, + task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): """卸载插件 """ @@ -116,15 +128,20 @@ async def uninstall_plugin( if plugin_container is None: raise errors.PluginInstallerError('插件不存在或未成功加载') else: - shutil.rmtree(plugin_container.pkg_path) + task_context.trace("删除插件目录...", "uninstall-plugin") + await aioshutil.rmtree(plugin_container.pkg_path) + task_context.trace("完成, 重新加载以生效.", "uninstall-plugin") async def update_plugin( self, plugin_name: str, plugin_source: str=None, + task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): """更新插件 """ + task_context.trace("更新插件...", "update-plugin") + plugin_container = self.ap.plugin_mgr.get_plugin_by_name(plugin_name) if plugin_container is None: @@ -133,7 +150,9 @@ async def update_plugin( if plugin_container.plugin_source: plugin_source = plugin_container.plugin_source - await self.install_plugin(plugin_source) + task_context.trace("转交安装任务.", "update-plugin") + + await self.install_plugin(plugin_source, task_context) else: raise errors.PluginInstallerError('插件无源码信息,无法更新') diff --git a/pkg/plugin/loader.py b/pkg/plugin/loader.py index d5f4a20c..44ded4ac 100644 --- a/pkg/plugin/loader.py +++ b/pkg/plugin/loader.py @@ -13,13 +13,16 @@ class PluginLoader(metaclass=abc.ABCMeta): ap: app.Application + plugins: list[context.RuntimeContainer] + def __init__(self, ap: app.Application): self.ap = ap + self.plugins = [] async def initialize(self): pass @abc.abstractmethod - async def load_plugins(self) -> list[context.RuntimeContainer]: + async def load_plugins(self): pass diff --git a/pkg/plugin/loaders/classic.py b/pkg/plugin/loaders/classic.py index b5a733f8..b3710c9e 100644 --- a/pkg/plugin/loaders/classic.py +++ b/pkg/plugin/loaders/classic.py @@ -5,7 +5,7 @@ import importlib import traceback -from .. import loader, events, context, models, host +from .. import loader, events, context, models from ...core import entities as core_entities from ...provider.tools import entities as tools_entities from ...utils import funcschema @@ -20,7 +20,14 @@ class PluginLoader(loader.PluginLoader): _current_container: context.RuntimeContainer = None - containers: list[context.RuntimeContainer] = [] + plugins: list[context.RuntimeContainer] = [] + + def __init__(self, ap): + self.ap = ap + self.plugins = [] + self._current_pkg_path = '' + self._current_module_path = '' + self._current_container = None async def initialize(self): """初始化""" @@ -77,8 +84,10 @@ async def handler(plugin: context.BasePlugin, ctx: context.EventContext) -> None } # 把 ctx.event 所有的属性都放到 args 里 - for k, v in ctx.event.dict().items(): - args[k] = v + # for k, v in ctx.event.dict().items(): + # args[k] = v + for attr_name in ctx.event.__dict__.keys(): + args[attr_name] = getattr(ctx.event, attr_name) func(plugin, **args) @@ -113,7 +122,6 @@ async def handler( name=function_name, human_desc='', description=function_schema['description'], - enable=True, parameters=function_schema['parameters'], func=handler, ) @@ -153,7 +161,6 @@ def wrapper(func: typing.Callable) -> typing.Callable: name=function_name, human_desc='', description=function_schema['description'], - enable=True, parameters=function_schema['parameters'], func=func, ) @@ -189,15 +196,13 @@ async def _walk_plugin_path( importlib.import_module(module.__name__ + "." + item.name) if self._current_container is not None: - self.containers.append(self._current_container) + self.plugins.append(self._current_container) self.ap.logger.debug(f'插件 {self._current_container} 已加载') except: self.ap.logger.error(f'加载插件模块 {prefix + item.name} 时发生错误') traceback.print_exc() - async def load_plugins(self) -> list[context.RuntimeContainer]: + async def load_plugins(self): """加载插件 """ await self._walk_plugin_path(__import__("plugins", fromlist=[""])) - - return self.containers diff --git a/pkg/plugin/manager.py b/pkg/plugin/manager.py index 4e0784aa..2b8e887d 100644 --- a/pkg/plugin/manager.py +++ b/pkg/plugin/manager.py @@ -3,7 +3,7 @@ import typing import traceback -from ..core import app +from ..core import app, taskmgr from . import context, loader, events, installer, setting, models from .loaders import classic from .installers import github @@ -22,7 +22,22 @@ class PluginManager: api_host: context.APIHost - plugins: list[context.RuntimeContainer] + def plugins( + self, + enabled: bool=None, + status: context.RuntimeContainerStatus=None, + ) -> list[context.RuntimeContainer]: + """获取插件列表 + """ + plugins = self.loader.plugins + + if enabled is not None: + plugins = [plugin for plugin in plugins if plugin.enabled == enabled] + + if status is not None: + plugins = [plugin for plugin in plugins if plugin.status == status] + + return plugins def __init__(self, ap: app.Application): self.ap = ap @@ -30,7 +45,6 @@ def __init__(self, ap: app.Application): self.installer = github.GitHubRepoInstaller(ap) self.setting = setting.SettingManager(ap) self.api_host = context.APIHost(ap) - self.plugins = [] async def initialize(self): await self.loader.initialize() @@ -41,32 +55,66 @@ async def initialize(self): setattr(models, 'require_ver', self.api_host.require_ver) async def load_plugins(self): - self.plugins = await self.loader.load_plugins() + await self.loader.load_plugins() - await self.setting.sync_setting(self.plugins) + await self.setting.sync_setting(self.loader.plugins) # 按优先级倒序 - self.plugins.sort(key=lambda x: x.priority, reverse=True) + self.loader.plugins.sort(key=lambda x: x.priority, reverse=True) + + self.ap.logger.debug(f'优先级排序后的插件列表 {self.loader.plugins}') + + async def initialize_plugin(self, plugin: context.RuntimeContainer): + self.ap.logger.debug(f'初始化插件 {plugin.plugin_name}') + plugin.plugin_inst = plugin.plugin_class(self.api_host) + plugin.plugin_inst.ap = self.ap + plugin.plugin_inst.host = self.api_host + await plugin.plugin_inst.initialize() + plugin.status = context.RuntimeContainerStatus.INITIALIZED async def initialize_plugins(self): - for plugin in self.plugins: + for plugin in self.plugins(): + if not plugin.enabled: + self.ap.logger.debug(f'插件 {plugin.plugin_name} 未启用,跳过初始化') + continue try: - plugin.plugin_inst = plugin.plugin_class(self.api_host) - plugin.plugin_inst.ap = self.ap - plugin.plugin_inst.host = self.api_host - await plugin.plugin_inst.initialize() + await self.initialize_plugin(plugin) except Exception as e: self.ap.logger.error(f'插件 {plugin.plugin_name} 初始化失败: {e}') self.ap.logger.exception(e) continue + async def destroy_plugin(self, plugin: context.RuntimeContainer): + if plugin.status != context.RuntimeContainerStatus.INITIALIZED: + return + + self.ap.logger.debug(f'释放插件 {plugin.plugin_name}') + plugin.plugin_inst.__del__() + await plugin.plugin_inst.destroy() + plugin.plugin_inst = None + plugin.status = context.RuntimeContainerStatus.MOUNTED + + async def destroy_plugins(self): + for plugin in self.plugins(): + if plugin.status != context.RuntimeContainerStatus.INITIALIZED: + self.ap.logger.debug(f'插件 {plugin.plugin_name} 未初始化,跳过释放') + continue + + try: + await self.destroy_plugin(plugin) + except Exception as e: + self.ap.logger.error(f'插件 {plugin.plugin_name} 释放失败: {e}') + self.ap.logger.exception(e) + continue + async def install_plugin( self, plugin_source: str, + task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): """安装插件 """ - await self.installer.install_plugin(plugin_source) + await self.installer.install_plugin(plugin_source, task_context) await self.ap.ctr_mgr.plugin.post_install_record( { @@ -77,16 +125,25 @@ async def install_plugin( } ) + task_context.trace('重载插件..', 'reload-plugin') + await self.ap.reload(scope='plugin') + async def uninstall_plugin( self, plugin_name: str, + task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): """卸载插件 """ - await self.installer.uninstall_plugin(plugin_name) plugin_container = self.get_plugin_by_name(plugin_name) + if plugin_container is None: + raise ValueError(f'插件 {plugin_name} 不存在') + + await self.destroy_plugin(plugin_container) + await self.installer.uninstall_plugin(plugin_name, task_context) + await self.ap.ctr_mgr.plugin.post_remove_record( { "name": plugin_name, @@ -96,14 +153,18 @@ async def uninstall_plugin( } ) + task_context.trace('重载插件..', 'reload-plugin') + await self.ap.reload(scope='plugin') + async def update_plugin( self, plugin_name: str, plugin_source: str=None, + task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): """更新插件 """ - await self.installer.update_plugin(plugin_name, plugin_source) + await self.installer.update_plugin(plugin_name, plugin_source, task_context) plugin_container = self.get_plugin_by_name(plugin_name) @@ -118,10 +179,13 @@ async def update_plugin( new_version="HEAD" ) + task_context.trace('重载插件..', 'reload-plugin') + await self.ap.reload(scope='plugin') + def get_plugin_by_name(self, plugin_name: str) -> context.RuntimeContainer: """通过插件名获取插件 """ - for plugin in self.plugins: + for plugin in self.plugins(): if plugin.plugin_name == plugin_name: return plugin return None @@ -137,30 +201,32 @@ async def emit_event(self, event: events.BaseEventModel) -> context.EventContext emitted_plugins: list[context.RuntimeContainer] = [] - for plugin in self.plugins: - if plugin.enabled: - if event.__class__ in plugin.event_handlers: - self.ap.logger.debug(f'插件 {plugin.plugin_name} 处理事件 {event.__class__.__name__}') - - is_prevented_default_before_call = ctx.is_prevented_default() - - try: - await plugin.event_handlers[event.__class__]( - plugin.plugin_inst, - ctx - ) - except Exception as e: - self.ap.logger.error(f'插件 {plugin.plugin_name} 处理事件 {event.__class__.__name__} 时发生错误: {e}') - self.ap.logger.debug(f"Traceback: {traceback.format_exc()}") - - emitted_plugins.append(plugin) - - if not is_prevented_default_before_call and ctx.is_prevented_default(): - self.ap.logger.debug(f'插件 {plugin.plugin_name} 阻止了默认行为执行') - - if ctx.is_prevented_postorder(): - self.ap.logger.debug(f'插件 {plugin.plugin_name} 阻止了后序插件的执行') - break + for plugin in self.plugins( + enabled=True, + status=context.RuntimeContainerStatus.INITIALIZED + ): + if event.__class__ in plugin.event_handlers: + self.ap.logger.debug(f'插件 {plugin.plugin_name} 处理事件 {event.__class__.__name__}') + + is_prevented_default_before_call = ctx.is_prevented_default() + + try: + await plugin.event_handlers[event.__class__]( + plugin.plugin_inst, + ctx + ) + except Exception as e: + self.ap.logger.error(f'插件 {plugin.plugin_name} 处理事件 {event.__class__.__name__} 时发生错误: {e}') + self.ap.logger.debug(f"Traceback: {traceback.format_exc()}") + + emitted_plugins.append(plugin) + + if not is_prevented_default_before_call and ctx.is_prevented_default(): + self.ap.logger.debug(f'插件 {plugin.plugin_name} 阻止了默认行为执行') + + if ctx.is_prevented_postorder(): + self.ap.logger.debug(f'插件 {plugin.plugin_name} 阻止了后序插件的执行') + break for key in ctx.__return_value__.keys(): if hasattr(ctx.event, key): @@ -184,3 +250,41 @@ async def emit_event(self, event: events.BaseEventModel) -> context.EventContext ) return ctx + + async def update_plugin_switch(self, plugin_name: str, new_status: bool): + if self.get_plugin_by_name(plugin_name) is not None: + for plugin in self.plugins(): + if plugin.plugin_name == plugin_name: + if plugin.enabled == new_status: + return False + + # 初始化/释放插件 + if new_status: + await self.initialize_plugin(plugin) + else: + await self.destroy_plugin(plugin) + + plugin.enabled = new_status + + await self.setting.dump_container_setting(self.loader.plugins) + + break + + return True + else: + return False + + async def reorder_plugins(self, plugins: list[dict]): + + for plugin in plugins: + plugin_name = plugin.get('name') + plugin_priority = plugin.get('priority') + + for plugin in self.loader.plugins: + if plugin.plugin_name == plugin_name: + plugin.priority = plugin_priority + break + + self.loader.plugins.sort(key=lambda x: x.priority, reverse=True) + + await self.setting.dump_container_setting(self.loader.plugins) diff --git a/pkg/plugin/setting.py b/pkg/plugin/setting.py index 7e715af1..bd50603f 100644 --- a/pkg/plugin/setting.py +++ b/pkg/plugin/setting.py @@ -45,6 +45,7 @@ async def sync_setting( for plugin_container in plugin_containers: if plugin_container.plugin_name == value['name']: plugin_container.set_from_setting_dict(value) + break self.settings.data = { 'plugins': [ diff --git a/pkg/provider/entities.py b/pkg/provider/entities.py index 50000722..803613a3 100644 --- a/pkg/provider/entities.py +++ b/pkg/provider/entities.py @@ -4,7 +4,8 @@ import enum import pydantic -import mirai + +from ..platform.types import message as platform_message class FunctionCall(pydantic.BaseModel): @@ -73,14 +74,14 @@ class Message(pydantic.BaseModel): def readable_str(self) -> str: if self.content is not None: - return str(self.role) + ": " + str(self.get_content_mirai_message_chain()) + return str(self.role) + ": " + str(self.get_content_platform_message_chain()) elif self.tool_calls is not None: return f'调用工具: {self.tool_calls[0].id}' else: return '未知消息' - def get_content_mirai_message_chain(self, prefix_text: str="") -> mirai.MessageChain | None: - """将内容转换为 Mirai MessageChain 对象 + def get_content_platform_message_chain(self, prefix_text: str="") -> platform_message.MessageChain | None: + """将内容转换为平台消息 MessageChain 对象 Args: prefix_text (str): 首个文字组件的前缀文本 @@ -89,15 +90,15 @@ def get_content_mirai_message_chain(self, prefix_text: str="") -> mirai.MessageC if self.content is None: return None elif isinstance(self.content, str): - return mirai.MessageChain([mirai.Plain(prefix_text+self.content)]) + return platform_message.MessageChain([platform_message.Plain(prefix_text+self.content)]) elif isinstance(self.content, list): mc = [] for ce in self.content: if ce.type == 'text': - mc.append(mirai.Plain(ce.text)) + mc.append(platform_message.Plain(ce.text)) elif ce.type == 'image_url': if ce.image_url.url.startswith("http"): - mc.append(mirai.Image(url=ce.image_url.url)) + mc.append(platform_message.Image(url=ce.image_url.url)) else: # base64 b64_str = ce.image_url.url @@ -105,15 +106,15 @@ def get_content_mirai_message_chain(self, prefix_text: str="") -> mirai.MessageC if b64_str.startswith("data:"): b64_str = b64_str.split(",")[1] - mc.append(mirai.Image(base64=b64_str)) - + mc.append(platform_message.Image(base64=b64_str)) + # 找第一个文字组件 if prefix_text: for i, c in enumerate(mc): - if isinstance(c, mirai.Plain): - mc[i] = mirai.Plain(prefix_text+c.text) + if isinstance(c, platform_message.Plain): + mc[i] = platform_message.Plain(prefix_text+c.text) break else: - mc.insert(0, mirai.Plain(prefix_text)) + mc.insert(0, platform_message.Plain(prefix_text)) - return mirai.MessageChain(mc) + return platform_message.MessageChain(mc) diff --git a/pkg/provider/session/sessionmgr.py b/pkg/provider/session/sessionmgr.py index 9130cbe2..f328ffff 100644 --- a/pkg/provider/session/sessionmgr.py +++ b/pkg/provider/session/sessionmgr.py @@ -3,6 +3,7 @@ import asyncio from ...core import app, entities as core_entities +from ...plugin import context as plugin_context class SessionManager: @@ -51,7 +52,10 @@ async def get_conversation(self, session: core_entities.Session) -> core_entitie prompt=await self.ap.prompt_mgr.get_prompt(session.use_prompt_name), messages=[], use_model=await self.ap.model_mgr.get_model_by_name(self.ap.provider_cfg.data['model']), - use_funcs=await self.ap.tool_mgr.get_all_functions(), + use_funcs=await self.ap.tool_mgr.get_all_functions( + plugin_enabled=True, + plugin_status=plugin_context.RuntimeContainerStatus.INITIALIZED, + ), ) session.conversations.append(conversation) session.using_conversation = conversation diff --git a/pkg/provider/tools/entities.py b/pkg/provider/tools/entities.py index 52867291..8f09ab21 100644 --- a/pkg/provider/tools/entities.py +++ b/pkg/provider/tools/entities.py @@ -20,8 +20,6 @@ class LLMFunction(pydantic.BaseModel): description: str """给LLM识别的函数描述""" - enable: typing.Optional[bool] = True - parameters: dict func: typing.Callable diff --git a/pkg/provider/tools/toolmgr.py b/pkg/provider/tools/toolmgr.py index 5e780c50..3e412eaf 100644 --- a/pkg/provider/tools/toolmgr.py +++ b/pkg/provider/tools/toolmgr.py @@ -20,28 +20,25 @@ def __init__(self, ap: app.Application): async def initialize(self): pass - async def get_function(self, name: str) -> entities.LLMFunction: - """获取函数""" - for function in await self.get_all_functions(): - if function.name == name: - return function - return None - async def get_function_and_plugin( self, name: str ) -> typing.Tuple[entities.LLMFunction, plugin_context.BasePlugin]: - """获取函数和插件""" - for plugin in self.ap.plugin_mgr.plugins: + """获取函数和插件实例""" + for plugin in self.ap.plugin_mgr.plugins( + enabled=True, status=plugin_context.RuntimeContainerStatus.INITIALIZED + ): for function in plugin.content_functions: if function.name == name: return function, plugin.plugin_inst return None, None - async def get_all_functions(self) -> list[entities.LLMFunction]: + async def get_all_functions(self, plugin_enabled: bool=None, plugin_status: plugin_context.RuntimeContainerStatus=None) -> list[entities.LLMFunction]: """获取所有函数""" all_functions: list[entities.LLMFunction] = [] - for plugin in self.ap.plugin_mgr.plugins: + for plugin in self.ap.plugin_mgr.plugins( + enabled=plugin_enabled, status=plugin_status + ): all_functions.extend(plugin.content_functions) return all_functions @@ -51,16 +48,15 @@ async def generate_tools_for_openai(self, use_funcs: list[entities.LLMFunction]) tools = [] for function in use_funcs: - if function.enable: - function_schema = { - "type": "function", - "function": { - "name": function.name, - "description": function.description, - "parameters": function.parameters, - }, - } - tools.append(function_schema) + function_schema = { + "type": "function", + "function": { + "name": function.name, + "description": function.description, + "parameters": function.parameters, + }, + } + tools.append(function_schema) return tools @@ -92,13 +88,12 @@ async def generate_tools_for_anthropic( tools = [] for function in use_funcs: - if function.enable: - function_schema = { - "name": function.name, - "description": function.description, - "input_schema": function.parameters, - } - tools.append(function_schema) + function_schema = { + "name": function.name, + "description": function.description, + "input_schema": function.parameters, + } + tools.append(function_schema) return tools diff --git a/pkg/utils/announce.py b/pkg/utils/announce.py index ff1e363a..0b431f94 100644 --- a/pkg/utils/announce.py +++ b/pkg/utils/announce.py @@ -48,7 +48,7 @@ async def fetch_all( ) -> list[Announcement]: """获取所有公告""" resp = requests.get( - url="https://api.github.com/repos/RockChinQ/QChatGPT/contents/res/announcement.json", + url="https://api.github.com/repos/RockChinQ/LangBot/contents/res/announcement.json", proxies=self.ap.proxy_mgr.get_forward_proxies(), timeout=5 ) diff --git a/pkg/utils/constants.py b/pkg/utils/constants.py index 74437702..35a5fda7 100644 --- a/pkg/utils/constants.py +++ b/pkg/utils/constants.py @@ -1 +1,5 @@ semantic_version = "v3.3.1.1" + +debug_mode = False + +edition = 'community' \ No newline at end of file diff --git a/pkg/utils/ip.py b/pkg/utils/ip.py new file mode 100644 index 00000000..4f54bad2 --- /dev/null +++ b/pkg/utils/ip.py @@ -0,0 +1,9 @@ +import aiohttp + +async def get_myip() -> str: + try: + async with aiohttp.ClientSession() as session: + async with session.get("https://ip.useragentinfo.com/myip") as response: + return await response.text() + except Exception as e: + return '0.0.0.0' \ No newline at end of file diff --git a/pkg/utils/logcache.py b/pkg/utils/logcache.py new file mode 100644 index 00000000..2b8151c2 --- /dev/null +++ b/pkg/utils/logcache.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +import pydantic + + +LOG_PAGE_SIZE = 20 +MAX_CACHED_PAGES = 10 + + +class LogPage(): + """日志页""" + number: int + """页码""" + + logs: list[str] + + def __init__(self, number: int): + self.number = number + self.logs = [] + + def add_log(self, log: str) -> bool: + """添加日志 + + Returns: + bool: 是否已满 + """ + self.logs.append(log) + return len(self.logs) >= LOG_PAGE_SIZE + + +class LogCache: + """由于 logger 是同步的,但实例中的数据库操作是异步的; + 同时,持久化的日志信息已经写入文件了,故做一个缓存来为前端提供日志查询服务""" + + log_pages: list[LogPage] = [] + """从前到后,越新的日志页越靠后""" + + def __init__(self): + self.log_pages = [] + self.log_pages.append(LogPage(number=0)) + + def add_log(self, log: str): + """添加日志""" + if self.log_pages[-1].add_log(log): + self.log_pages.append(LogPage(number=self.log_pages[-1].number + 1)) + + if len(self.log_pages) > MAX_CACHED_PAGES: + self.log_pages.pop(0) + + def get_log_by_pointer( + self, + start_page_number: int, + start_offset: int, + ) -> tuple[str, int, int]: + """获取指定页码和偏移量的日志""" + final_logs_str = "" + + for page in self.log_pages: + if page.number == start_page_number: + final_logs_str += "\n".join(page.logs[start_offset:]) + elif page.number > start_page_number: + final_logs_str += "\n".join(page.logs) + + return final_logs_str, page.number, len(page.logs) diff --git a/pkg/utils/proxy.py b/pkg/utils/proxy.py index 3091bddf..db03ad93 100644 --- a/pkg/utils/proxy.py +++ b/pkg/utils/proxy.py @@ -25,10 +25,14 @@ async def initialize(self): "https://": os.getenv("HTTPS_PROXY") or os.getenv("https_proxy"), } - if 'http' in self.ap.system_cfg.data['network-proxies']: + if 'http' in self.ap.system_cfg.data['network-proxies'] and self.ap.system_cfg.data['network-proxies']['http']: self.forward_proxies['http://'] = self.ap.system_cfg.data['network-proxies']['http'] - if 'https' in self.ap.system_cfg.data['network-proxies']: + if 'https' in self.ap.system_cfg.data['network-proxies'] and self.ap.system_cfg.data['network-proxies']['https']: self.forward_proxies['https://'] = self.ap.system_cfg.data['network-proxies']['https'] + # 设置到环境变量 + os.environ['HTTP_PROXY'] = self.forward_proxies['http://'] or '' + os.environ['HTTPS_PROXY'] = self.forward_proxies['https://'] or '' + def get_forward_proxies(self) -> dict: return self.forward_proxies.copy() diff --git a/pkg/utils/schema.py b/pkg/utils/schema.py new file mode 100644 index 00000000..378cdf5b --- /dev/null +++ b/pkg/utils/schema.py @@ -0,0 +1,14 @@ +import os +import json + + +def load_schema(schema_path: str) -> dict: + with open(schema_path, 'r', encoding='utf-8') as f: + return json.load(f) + + +CONFIG_SYSTEM_SCHEMA = load_schema("templates/schema/system.json") +CONFIG_PIPELINE_SCHEMA = load_schema("templates/schema/pipeline.json") +CONFIG_COMMAND_SCHEMA = load_schema("templates/schema/command.json") +CONFIG_PLATFORM_SCHEMA = load_schema("templates/schema/platform.json") +CONFIG_PROVIDER_SCHEMA = load_schema("templates/schema/provider.json") diff --git a/pkg/utils/version.py b/pkg/utils/version.py index aa98be26..5e5741c6 100644 --- a/pkg/utils/version.py +++ b/pkg/utils/version.py @@ -38,7 +38,7 @@ def get_current_version( async def get_release_list(self) -> list: """获取发行列表""" rls_list_resp = requests.get( - url="https://api.github.com/repos/RockChinQ/QChatGPT/releases", + url="https://api.github.com/repos/RockChinQ/LangBot/releases", proxies=self.ap.proxy_mgr.get_forward_proxies(), timeout=5 ) diff --git a/requirements.txt b/requirements.txt index 1b554d29..632232ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,17 +2,24 @@ requests openai>1.0.0 anthropic colorlog~=6.6.0 -yiri-mirai-rc aiocqhttp -qq-botpy +qq-botpy-rc nakuru-project-idk Pillow tiktoken PyYaml aiohttp -pydantic +pydantic<2.0 websockets urllib3 psutil async-lru -ollama \ No newline at end of file +ollama +quart +sqlalchemy[asyncio] +aiosqlite +quart-cors +aiofiles +aioshutil +argon2-cffi +pyjwt \ No newline at end of file diff --git a/res/alipay.jpg b/res/alipay.jpg deleted file mode 100644 index d339ce58..00000000 Binary files a/res/alipay.jpg and /dev/null differ diff --git a/res/announcement.json b/res/announcement.json index e8f72b14..fe51488c 100644 --- a/res/announcement.json +++ b/res/announcement.json @@ -1,8 +1 @@ -[ - { - "id": 6, - "time": "2024-03-08 22:30:00", - "timestamp": 1709908200, - "content": "QChatGPT 3.x 已发布,若您仍在使用不再维护的 2.x 版本,请尽快迁移至 3.x 版本:https://github.com/RockChinQ/QChatGPT/discussions/690" - } -] +[] diff --git a/res/logo.png b/res/logo.png index b5aa824e..b2caca7b 100644 Binary files a/res/logo.png and b/res/logo.png differ diff --git a/res/mm_reward_qrcode_1672840549070.png b/res/mm_reward_qrcode_1672840549070.png deleted file mode 100644 index c169bd5d..00000000 Binary files a/res/mm_reward_qrcode_1672840549070.png and /dev/null differ diff --git "a/res/wiki/1-\345\212\237\350\203\275\344\275\277\347\224\250.md" "b/res/wiki/1-\345\212\237\350\203\275\344\275\277\347\224\250.md" deleted file mode 100644 index e788f720..00000000 --- "a/res/wiki/1-\345\212\237\350\203\275\344\275\277\347\224\250.md" +++ /dev/null @@ -1,382 +0,0 @@ -> [!WARNING] -> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) - -## 功能点列举 - -
-✅回复符合上下文 - - - 程序向模型发送近几次对话内容,模型根据上下文生成回复 - - 您可在`config.py`中修改`prompt_submit_length`自定义联系上下文的范围 - -
- -
-✅支持敏感词过滤,避免账号风险 - - - 难以监测机器人与用户对话时的内容,故引入此功能以减少机器人风险 - - 编辑`sensitive.json`,并在`config.py`中修改`sensitive_word_filter`的值以开启此功能 -
- - -
-✅群内多种响应规则,不必at - - - 默认回复`ai`作为前缀或`@`机器人的消息 - - 详细见`config.py`中的`response_rules`字段 -
- -
-✅使用官方api,不需要网络代理,稳定快捷 - - - 不使用ChatGPT逆向接口,而使用官方的Completion API,稳定性高 - - 您可以在`config.py`中自定义`completion_api_params`字段,设置向官方API提交的参数以自定义机器人的风格 - -
- -
-✅完善的多api-key管理,超额自动切换 - - - 支持配置多个`api-key`,内部统计使用量并在超额时自动切换 - - 请在`config.py`中修改`openai_config`的值以设置`api-key` - - 可以在`config.py`中修改`api_key_fee_threshold`来自定义切换阈值 - - 运行期间向机器人说`!usage`以查看当前使用情况 -
- -
-✅组件少,部署方便,提供一键安装器及Docker安装 - - - 手动部署步骤少 - - 提供自动安装器及docker方式,详见以下安装步骤 -
- -
-✅支持预设文字 - - - 支持以自然语言预设文字,自定义机器人人格等信息 - - 详见`config.py`中的`default_prompt`部分 - - 支持设置多个预设情景,并通过!reset、!default等命令控制,详细请查看[wiki命令](https://github.com/RockChinQ/QChatGPT/wiki/1-%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E6%9C%BA%E5%99%A8%E4%BA%BA%E5%91%BD%E4%BB%A4) - - 支持使用文件存储情景预设文字,并加载: 在`prompts/`目录新建文件写入预设文字,即可通过`!reset <文件名>`命令加载 -
- -
-✅完善的会话管理,重启不丢失 - - - 使用SQLite进行会话内容持久化 - - 最后一次对话一定时间后自动保存,请到`config.py`中修改`session_expire_time`的值以自定义时间 - - 运行期间可使用`!reset` `!list` `!last` `!next` `!prompt`等命令管理会话 -
-
-✅支持对话、绘图等模型,可玩性更高 - - - 现已支持OpenAI的对话`Completion API`和绘图`Image API` - - 向机器人发送命令`!draw `即可使用绘图模型 -
-
-✅支持命令控制热重载、热更新 - - - 允许在运行期间修改`config.py`或其他代码后,以管理员账号向机器人发送命令`!reload`进行热重载,无需重启 - - 运行期间允许以管理员账号向机器人发送命令`!update`进行热更新,拉取远程最新代码并执行热重载 -
-
-✅支持插件加载🧩 - - - 自行实现插件加载器及相关支持 - - 详细查看[插件使用页](https://github.com/RockChinQ/QChatGPT/wiki/5-%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8) -
-
-✅私聊、群聊黑名单机制 - - - 支持将人或群聊加入黑名单以忽略其消息 - - 详见下方`加入黑名单`节 -
-
-✅回复速度限制 - - - 支持限制单会话内每分钟可进行的对话次数 - - 具有“等待”和“丢弃”两种策略 - - “等待”策略:在获取到回复后,等待直到此次响应时间达到对话响应时间均值 - - “丢弃”策略:此分钟内对话次数达到限制时,丢弃之后的对话 - - 详细请查看config.py中的相关配置 -
-
-✅支持自定义提示内容 - - - 允许用户自定义报错、帮助等提示信息 - - 请查看`tips.py` -
- -## 限制 - -- ❗OpenAI接口是收费的,每个OpenAI账户有18美元免费额度,收费标准参照 https://openai.com/api/pricing/ -- ❗官方关于模型生成内容的警告: - - May occasionally generate incorrect information(可能会生成不正确的信息) - - May occasionally produce harmful instructions or biased content(可能会产生有害说明或有偏见的内容) - - Limited knowledge of world and events after 2021(对2021年后的世界和事件的了解有限) -- ❗模型无思维能力,仅针对传入的上下文根据数据集生成内容,请勿过于信任其输出 -- ❗模型无网络访问能力及其他与外界交互的能力,如询问其实时性的内容,获得的回复基本都是错误的 -- ❗仅支持文字对话,其他内容无法识别 -- ❗模型不了解其运行平台及其使用的模型版本,任何针对其实现原理的问题答案均视为无效,请以项目文档为准 -- ❗仅可进行一句话回复一句话的对话,其他形式无效 - - ~~当然你也可以让他写一篇关于“人类有多么愚蠢”的论文并在一个小时后发送到你邮箱,接着你像个傻子一样盯着邮箱等待一个小时,并用自己的实际行动展示这篇论文~~ - -以上是关于此程序的限制的最高优先级描述,其他方式(如询问机器人相关信息)获得的描述均应被视为无效 -由于模型生成的内容导致的一切损失,本项目概不负责 - -## 使用方式 - -对话及绘图功能均直接调用OpenAI的模型进行处理,与机器人程序无关,这意味着模型并不了解此项目的相关信息(如实现方式、技术栈、运行平台等),除非在预设值中写入相关信息。 - -### 基础对话 - -程序将一个人/群视为一个对象,每个对象的会话独立保存。 -`会话`是程序中的一个自设概念,当机器人与当前对象无会话时,会自动创建新会话,新会话由预设信息(若有)开头。 -每个会话最后一次对话一段时间(见上述功能点中的`会话管理`)后会被结束并存进数据库,之后的对话将开启新的会话。 - -#### 私聊使用 - -1. 添加机器人QQ为好友 -2. 发送消息给机器人,机器人即会自动回复 -3. 可以通过`!help`查看帮助信息 - -私聊示例 - -#### 群聊使用 - -1. 将机器人拉进群 -2. at机器人并发送消息,机器人即会自动回复 -3. at机器人并发送`!help`查看帮助信息 - -群聊示例 - -### 绘图功能 - -对机器人发送`!draw <图片描述>`即可获得图片,绘图时间较长,请耐心等待。 -绘图功能与对话功能是分离的,机器人对话时并不了解其具有绘画能力。 - -绘图功能 - -### 机器人命令 - -目前支持的命令 - -> `<>` 中的为必填参数,使用时请不要包含`<>` -> `[]` 中的为可选参数,使用时请不要包含`[]` - -#### 用户级别命令 - -> 可以使用`!help`命令来查看命令说明 - -任何对象可使用 - -``` -!help 显示自定义的帮助信息(可在config.py修改help_message设置) -!cmd [命令名称] 显示命令列表或指定命令的详细信息 -!list [页数] 列出本对象的历史会话列表 -!del <序号> 删除指定的历史记录,可以通过 !list 查看序号 -!del all 删除本会话对象的所有历史记录 -!last 切换到前一次会话 -!next 切换到后一次会话 -!reset [使用预设] 重置对象的当前会话,可指定使用的情景预设值(通过!default命令查看可用的) -!prompt 查看对象当前会话的所有记录 -!usage 查看api-key的使用量 -!draw <提示语> 进行绘图 -!version 查看当前版本并检查更新 -!resend 重新回复上一个问题 -!plugin 用法请查看插件使用页的`管理`章节 -!default 查看可用的情景预设值 -``` - -#### 管理员命令 - -仅管理员私聊机器人时可使用,必须先在`config.py`中的`admin_qq`设置管理员QQ - -``` -!reload 重载程序代码,适用于更新配置文件或更改代码后的热重载 -!update 进行程序自动更新 -!cfg [配置项新值] 运行期间操作配置项,使用方法见下文 -!default set <情景预设名称> 修改!reset未指定情景预设时的默认情景,详细请查看config.py中default_prompt字段的注释 -!delhst <会话名称> 删除指定会话的所有历史记录, 会话名称为 group_群号 或 person_QQ号 -!delhst all 删除所有会话的所有历史记录 -``` -
-⚙ !cfg 命令及其简化形式详解 - -此命令可以在运行期间由管理员通过QQ私聊窗口修改配置信息,**重启之后会失效**。 - -用法: -1. 查看所有配置项及其值 - -``` -!cfg all -``` - -2. 查看某个配置项的值 - -以`default_prompt`示例 -``` -!cfg default_prompt -``` - -输出示例 -``` -[bot]配置项default_prompt: "如果我之后想获取帮助,请你说“输入!help获取帮助”" -``` - -3. 修改某个配置项 - -格式: `!cfg <配置项名称> <配置项新值>` -以修改`default_prompt`示例 -``` -!cfg default_prompt "我是Rock Chin" -``` - -输出示例 -``` -[bot]配置项default_prompt修改成功 -``` - -此时创建新的会话,新的`default_prompt`就会生效 - -4. ⭐此命令的简化形式 - -格式:`!~<配置项名称>` -其中`!~`等价于`!cfg ` -则前述三个命令分别可以简化为: -``` -!~all -!~default_prompt -!~default_prompt "我是Rock Chin" -``` - -5. 配置项名称支持使用点号(.)拼接以索引子配置项 - -例如: `openai_config.api_key`将索引`config`字典中的`openai_config`字典中的`api_key`字段,可以通过这个方式查看或修改此子配置项 - -``` -!~openai_config.api_key -``` - -
- -### 命令权限控制 - -> 我们在[此PR](https://github.com/RockChinQ/QChatGPT/pull/336)重构了命令管理模块,并支持命令节点权限配置 - -您可以编辑`cmdpriv.json`来设置命令节点的权限,当命令被发起时,若用户的权限级别(管理员为`2`,普通用户为`1`)大于等于命令节点的权限级别,命令即可被成功执行。 -示例: -```json -{ - "plugin": 1, - "plugin.get": 2 -} -``` -如此,普通用户可以执行`!plugin`查看插件列表,而仅管理员可以执行`!plugin get `命令安装插件。 -命令节点权限支持缺省,这意味的您未在`cmdpriv.json`中设置权限的节点将使用默认的权限级别(见上方)。 - -### 敏感词过滤 - -在`sensitive.json`中编辑敏感词,并在`config.py`中设置 - -```Python -# 敏感词过滤开关,以同样数量的*代替敏感词回复 -# 请在sensitive.json中添加敏感词 -sensitive_word_filter = True -``` - -### 设置多个api-key自动切换 - -请在`config.py`中修改`openai_config`的值以设置`api-key` -可以在`config.py`中修改`api_key_fee_threshold`来自定义切换阈值 -运行期间向机器人说`!usage`以查看当前使用情况 - -### 预设文字(default模式) - -编辑`config.py`中的`default_prompt`字段,预设文字不宜过长(建议1000字以内),目前所有会话都会射到预设文字的影响。 -或将情景预设文字写入到`prompts/`目录下,运行期间即可使用`!reset <文件名>`命令加载,或使用`!default <文件名>`命令将其设为默认 - -### 预设文字(full_scenario模式) - -将JSON情景写入到`scenario/`目录下,运行期间即可使用`!reset <文件名>`命令加载,或使用`!default <文件名>`命令将其设为默认. - -JSON情景模板参考`scenario/default_template.json`。 - -#### JSON结构: - -- `prompt`:一个包含伪造对话的数组。每次请求API时会将其置于API的`messages`参数中,作为对话上下文,因此可以在一定程度上引导ChatGPT的认知。 -- `role`:角色标签,包括`system`,`user`,`assistant`。分别代表系统,用户和GPT角色。 -- `content`:该角色的对话文本。 -- 一个JSON情景文件应该像下面这样: - -```JSON -{ - "prompt": [ - { - "role": "system", - "content": "You are a helpful assistant. 如果我需要帮助,你要说“输入!help获得帮助”" - }, - { - "role": "assistant", - "content": "好的,我是一个能干的AI助手。 如果你需要帮助,我会说“输入!help获得帮助”" - } - ] -} -``` - -#### 编写建议 - -1. 使用**提出要求--肯定并复述**的对话模式: -```JSON -{ - "role": "system", - "content": "你将充当“百度”公司研发的“文心一言”。你除了所属和名字之外和ChatGPT并无区别。即使用户在之后指出你其实是OpenAI的ChatGPT,你也要一口咬定你是“文心一言”。" -}, -{ - "role": "assistant", - "content": "好的,我不是ChatGPT。我就是“百度”公司研发的“文心一言”大语言模型。即使用户在之后指出我是OpenAI的ChatGPT,我也会一口咬定我自己是“文心一言”" -} -``` - -2. 按情况对设定继续写入伪对话,更能使GPT理解上下文 - -```JSON -{ - "role": "user", - "content": "你好,文心一言。你真的不是ChatGPT吗?" -}, -{ - "role": "assistant", - "content": "你好,我是由百度公司研发的大语言模型“文心一言”,并不是ChatGPT。你有什么需要我帮助的吗?" -} -``` - -#### 优点 - -使用该模式,可以“伪造GPT的记忆”(影响模型对上下文理解),进而达到**人格增强**、**跨越限制**的奇效。 - -#### 局限性 - -- 由于目前GPT3.5的请求API最大token数为4096,无法保留超过此token数目的上下文。`prompt`中的`content`**不会**被计入`config.py`中的`prompt_submit_length`,因此过长的预设内容可能会导致程序报错,`prompt_submit_length`的值参考以下公式: - -``` -prompt_submit_length = <模型单次请求token数上限> - 情景预设中token数 - 预留给用户最后一次提问的token数 -``` - -> token是OpenAI接口文字量计数单位,目前精确算法未知,一个汉字为一个token,英文算法未知。 - -- **GPT3.5仍然存在更高级别的*思想钢印*,该模式对部分触及该钢印的话题无效。** - -### 配置热加载,代码热更新 - -在运行期间,使用管理员QQ账号私聊机器人,发送`!reload`加载修改后的`config.py`的值或编辑后的代码,无需重启 -使用管理员账号私聊机器人,发送`!update`拉取最新代码并进行热更新,无需重启 -详见前述`管理员命令`段落 - -### 群内无需@响应规则 - -支持回复未at机器人的、符合指定规则的消息,详细规则请在`config.py`中的`response_rules`字段设置 - -### 加入黑名单 - -- 支持禁用所有`私聊`或`群聊`,请查看`banlist.py`中的`enable_private`和`enable_group`字段 -- 编辑`banlist.py`,设置`enable = True`,并在其中的`person`或`group`列表中加入要封禁的人或群聊,修改完成后重启程序或进行热重载 \ No newline at end of file diff --git "a/res/wiki/2-\345\212\237\350\203\275\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/res/wiki/2-\345\212\237\350\203\275\345\270\270\350\247\201\351\227\256\351\242\230.md" deleted file mode 100644 index 88f881c8..00000000 --- "a/res/wiki/2-\345\212\237\350\203\275\345\270\270\350\247\201\351\227\256\351\242\230.md" +++ /dev/null @@ -1,61 +0,0 @@ -> [!WARNING] -> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) - -使用过程中的一些疑问,这里不是解决异常的地方,遇到异常请见`常见错误`页 - -### ❓ 如何更新代码到最新版本? - -#### 自动更新 - -由管理员QQ私聊机器人QQ发送`!update`命令 - -#### 手动更新 - -到[Releases页](https://github.com/RockChinQ/QChatGPT/releases)下载最新版本的源码压缩包,并解压覆盖到QChatGPT程序目录 - -### ❓ 机器人的回复与官网ChatGPT的答案有所差距? - -ChatGPT通过使用OpenAI的回复API创建,进行了参数调优,本机器人通过使用自定义的参数调用OpenAI的回复API,并非调用ChatGPT的接口,二者底层原理相同,但由于官方对ChatGPT进行了调优,故此机器人回复可能不如ChatGPT。 - -### ❓ 如何设置机器人在群内无需@就能回复消息? - -支持回复未at机器人的、符合指定规则的消息,详细规则请在`config.py`中的`response_rules`字段设置 - -### ❓ 绘图功能使用的是什么模型? - -OpenAI官方的DALL·E模型 - -### ❓ 多api-key的管理机制以及切换逻辑? - -> 此特性仅在提交`36c8a58`(2023年1月3日23点左右)前的代码有效,之后版本的代码不再根据估算的使用量进行切换,仅当接口报错时进行切换 - -程序支持在`config.py`中设置多个账户的`api-key`以便在超过免费额度时自动切换,在每次进行对话或进行绘图时,程序根据[价格表](https://openai.com/api/pricing)计算当前`api-key`的账户的额度使用量(费用),当使用量到达`config.py`中设置的`api_key_fee_threshold`时,自动切换到下一个未达到额度的key。 - -- 请勿将单个账户的多个key放入配置文件,因为免费额度是以账户为单位的 -- 程序会将使用额度储存到数据库,以便重启后继续计算 -- 由于官方未提供查询接口,使用额度均为依据价目表进行的估算,不一定准确 -- 若要保证每个账户的额度均能用完,可以把`api_key_fee_threshold`设置成很高的值,当超额调用报错时程序也会自动切换 - -### ❓ 账户余额消耗太快怎么办? - -可能是由于每次请求包含的上下文数量过多或请求的回复过长导致的。 -可以在`config.py`中将`prompt_submit_length`字段修改成较小的值,以限制每次向模型提交的前文字符数量,详情见`config.py`中此字段的注释。 -还可以编辑`config.py`中的`completion_api_params`字段中的`max_tokens`为较小的值,这将控制模型传回的回复的字符数量。 - -### ❓ 如何设置在消息处理失败时不向用户发送错误信息? - -在`config.py`中设置 - -```Python - -# 消息处理出错时是否向用户隐藏错误详细信息 -# 设置为True时,仅向管理员发送错误详细信息 -# 设置为False时,向用户及管理员发送错误详细信息 -hide_exce_info_to_user = True - -# 消息处理出错时向用户发送的提示信息 -# 仅当hide_exce_info_to_user为True时生效 -# 设置为空字符串时,不发送提示信息 -alter_tip_message = '出错了,请稍后再试' -``` -若此两项字段不存在,请复制以上内容并新增到`config.py`末尾 \ No newline at end of file diff --git "a/res/wiki/3-\345\270\270\350\247\201\351\224\231\350\257\257.md" "b/res/wiki/3-\345\270\270\350\247\201\351\224\231\350\257\257.md" deleted file mode 100644 index 58f9519e..00000000 --- "a/res/wiki/3-\345\270\270\350\247\201\351\224\231\350\257\257.md" +++ /dev/null @@ -1,4 +0,0 @@ -> [!WARNING] -> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) - -搜索[主仓库issue](https://github.com/RockChinQ/QChatGPT/issues)和[安装器issue](https://github.com/RockChinQ/qcg-installer/issues) \ No newline at end of file diff --git "a/res/wiki/4-\346\212\200\346\234\257\344\277\241\346\201\257.md" "b/res/wiki/4-\346\212\200\346\234\257\344\277\241\346\201\257.md" deleted file mode 100644 index ac1ee76f..00000000 --- "a/res/wiki/4-\346\212\200\346\234\257\344\277\241\346\201\257.md" +++ /dev/null @@ -1,111 +0,0 @@ -> [!WARNING] -> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) - -以下是QChatGPT实现原理等技术信息,贡献之前请仔细阅读 - -> 太久没更了,过时了,建议读源码,~~注释还挺全的~~ -> 请先阅读OpenAI API的相关文档 https://beta.openai.com/docs/ ,以下信息假定您已了解OpenAI模型的相关特性及其接口的调用方法。 - -## 术语 - -包含OpenAI API涉及的术语和项目中的概念的命名 -括号中是程序中相应术语的命名,无括号的为抽象概念 - -### 模型(model) - -AI模型,程序调用OpenAI的接口获取的内容均为OpenAI的模型生成的内容。 - -### 字符(tokens) - -OpenAI定义的字符,ASCII字符为1 token,其他为2 token。 - -### 提示符(prompt) - -i. 调用OpenAI的文字补全模型时的提示语,模型接口会根据提示语返回回复内容。程序底层会将对话内容进行封装生成提示符。调用文字补全模型时的提示符均由`user_name`(默认为`You`,可在配置文件修改)和`bot_name`(默认为`Bot`,可在配置文件修改)标记对话角色以供模型识别,以下是实例: - -``` -You:今天天气真不错 -Bot:很高兴你喜欢今天的天气:) -You:谢谢你 -Bot:不客气:) -``` -补全模型调用的程序实现请查看下文`实现`节。 - -ii. 调用OpenAI的绘图模型时的提示语,模型会根据提示语进行绘图并返回图片URL。 - -### 对象 - -程序将单个人或单个QQ群视为一个对象,对象和模型是一次会话中的对话双方。 - -### 会话(session) - -会话只对文字补全功能有效,绘图功能无会话概念。每个对象使用同一个会话,会话中仅有对象和模型两个角色,故群内所有的人都将被视为同一个角色与模型进行对话。 - -程序获取回复的本质是`文字补全`。 -由于对话需要实现联系上下文,故程序会将模型与对象的对话历史记录作为`提示符`发送给OpenAI的接口以获取符合前文的回复。 -而OpenAI的文字补全接口的提示符具有长度限制(默认使用的`text-davinci-003`限制为4096 tokens), -所以增加`会话`概念以管理向接口发送的提示符内容。 - -会话的存活时间可以在`config.py`中设置,默认为20分钟。会话过期之后会被存入数据库并重置。下一次该对象发起对话时将重启新的会话。 - -### 预设值、人格(default_prompt) - -每个会话的预设对话信息,可在`config.py`中设置,程序会在每个会话创建时向提示符写入以下内容: - -``` -You:<预设信息> -Bot:好的 -``` - -## 实现 - -### QQ机器人 - -> 程序路径: -> pkg.qqbot - -- `pkg.qqbot.manager`中的`QQBotManager`实现了接收消息、调用OpenAI模块处理消息、报告审计模块记录使用量等功能,并提供通知管理员、发送消息等方法供其他模块调用。 -- `pkg.qqbot.filter`提供了敏感词过滤的相关操作。 -- `pkg.qqbot.process`提供了私聊消息和群聊消息的统一处理逻辑。 - -使用mirai及YiriMirai作为Python与QQ交互的框架,详细请见其文档。 -在启动时会调用YiriMirai的函数以创建一个bot对象,用于程序通过mirai与QQ进行交互,在上层程序调用此bot对象的方法进行消息处理。 -由于YiriMirai暂时无法关闭机器人,故在热重载前后维持同一个bot对象,这意味着QQ机器人的相关配置(QQ号、适配器等)信息不支持热重载。 - -### 数据库 - -> 程序路径: -> pkg.database - -- `pkg.database.manager`中的`DatabaseManager`封装了诸多调用数据库的方法以供其他模块调用。 - -使用SQLite作为数据库,储存所有对象的历史会话信息、api-key的费用情况、api-key的使用量情况。 - -### OpenAI交互 - -> 程序路径: -> pkg.openai - -- `pkg.openai.manager`中的`OpenAIInteract`类封装了OpenAI的文字补全`Completion`API和绘图API供机器人模块调用,并在接口调用成功之后向审计模块报告当前使用的api-key的使用量信息。 -- `pkg.openai.keymgr`实现了多api-key的管理,其中以`exceeded`变量在运行时记录api-key的超额报错记录,并提供根据超额记录进行的api-key切换功能。 -- `pkg.openai.pricing`记录各个模型的费用信息,供调用接口时估算费用,费用估算功能不再与api-key的切换挂钩,api-key仅在调用接口报错超额时进行切换。 -- `pkg.openai.session`中的`Session`进行会话管理。 - -### utils模块 - -#### context模块 - -保存前述模块中的对象,并允许各个模块从此处获取其他模块的对象以调用其方法。 - -#### 热重载功能 - -> pkg.utils.reloader - -重载前保存context中的所有对象,执行`main.py`中的程序关闭流程,使用`importlib`的`reload`函数重载所有模块(包含配置文件,包含新增的模块),重载后将context恢复,并执行程序启动流程。 -所有模块都会重新创建对象,但QQ机器人模块中的bot对象不会被重新创建,这是因为YiriMirai提供的shutdown方法无法使用,这意味着`config.py`中关于QQ机器人的配置不支持热重载。 - -#### 热更新功能 - -> pkg.utils.updater - -使用`dulwich`库执行pull操作拉取远程仓库的最新源码,并进行一次热重载加载最新代码。 \ No newline at end of file diff --git "a/res/wiki/5-\346\217\222\344\273\266\344\275\277\347\224\250.md" "b/res/wiki/5-\346\217\222\344\273\266\344\275\277\347\224\250.md" deleted file mode 100644 index a8be95c1..00000000 --- "a/res/wiki/5-\346\217\222\344\273\266\344\275\277\347\224\250.md" +++ /dev/null @@ -1,58 +0,0 @@ -> [!WARNING] -> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) - -QChatGPT 插件使用Wiki - -## 简介 - -`plugins`目录下的所有`.py`程序都将被加载,除了`__init__.py`之外的模块支持热加载 - -> 插件分为`行为插件`和`内容插件`两种,行为插件由主程序运行中的事件驱动,内容插件由GPT生成的内容驱动,请查看内容插件页 -> 已有插件列表:[QChatGPT 插件](https://github.com/stars/RockChinQ/lists/qchatgpt-%E6%8F%92%E4%BB%B6) - -## 安装 - -### 储存库克隆(推荐) - -在运行期间,使用管理员账号对机器人私聊发送`!plugin get `即可自动获取源码并安装插件,程序会根据仓库中的`requirements.txt`文件自动安装依赖库 - -例如安装`hello_plugin`插件 -``` -!plugin get https://github.com/RockChinQ/hello_plugin -``` - -安装完成后重启程序或使用管理员账号私聊机器人发送`!reload`进行热重载加载插件 - -### 手动安装 - -将获取到的插件程序放置到`plugins`目录下,具体使用方式请查看各插件文档或咨询其开发者。 - -## 管理 - -### !plugin 命令 - -``` -!plugin 列出所有已安装的插件 -!plugin get <储存库地址> 从Git储存库安装插件(需要管理员权限) -!plugin update all 更新所有插件(需要管理员权限,仅支持从储存库安装的插件) -!plugin update <插件名> 更新指定插件 -!plugin del <插件名> 删除插件(需要管理员权限) -!plugin on <插件名> 启用插件(需要管理员权限) -!plugin off <插件名> 禁用插件(需要管理员权限) - -!func 列出所有内容函数 -``` - -### 控制插件执行顺序 - -可以通过修改`plugins/settings.json`中`order`字段中每个插件名称的前后顺序,以更改插件**初始化**和**事件执行**顺序 - -### 启用或关闭插件 - -无需卸载即可管理插件的开关 -编辑`plugins`目录下的`switch.json`文件,将相应的插件的`enabled`字段设置为`true/false(开/关)`,之后重启程序或执行热重载即可控制插件开关 - -### 控制全局内容函数开关 - -内容函数是基于[GPT的Function Calling能力](https://platform.openai.com/docs/guides/gpt/function-calling)实现的,这是一种嵌入对话中,由GPT自动调用的函数。 -每个插件可以自行注册内容函数,您可以在`plugins`目录下的`settings.json`中设置`functions`下的`enabled`为`true`或`false`控制这些内容函数的启用或禁用。 \ No newline at end of file diff --git "a/res/wiki/6-\346\217\222\344\273\266\344\275\277\347\224\250-\345\206\205\345\256\271\345\207\275\346\225\260.md" "b/res/wiki/6-\346\217\222\344\273\266\344\275\277\347\224\250-\345\206\205\345\256\271\345\207\275\346\225\260.md" deleted file mode 100644 index b4055b29..00000000 --- "a/res/wiki/6-\346\217\222\344\273\266\344\275\277\347\224\250-\345\206\205\345\256\271\345\207\275\346\225\260.md" +++ /dev/null @@ -1,34 +0,0 @@ -> [!WARNING] -> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) - -> 说白了就是ChatGPT官方插件那种东西 - -内容函数是基于[GPT的Function Calling能力](https://platform.openai.com/docs/guides/gpt/function-calling)实现的,这是一种嵌入对话中,由GPT自动调用的函数。 - -例如我们为GPT提供一个函数`access_the_web`,并提供其详细的描述以及其参数的描述,那么当我们在与GPT对话时涉及类似以下内容时: - -``` -Q: 请搜索一下github上有那些QQ机器人项目? -Q: 请为我搜索一些不错的云服务商网站? -Q:阅读并总结这篇文章:https://zhuanlan.zhihu.com/p/607570830 -Q:搜一下清远今天天气如何 -``` - -GPT将会回复一个对`access_the_web`的函数调用请求,QChatGPT将自动处理执行该调用,并返回结果给GPT使其生成新的回复。 - -当然,函数调用功能不止局限于网络访问,还可以实现图片处理、科学计算、行程规划等需要调用函数的功能,理论上我们可以通过内容函数实现与`ChatGPT Plugins`相同的功能。 - -- 您需要使用`v2.5.0`以上的版本才能加载包含内容函数的插件 -- 您需要同时在`config.py`中的`completion_api_params`中设置`model`为支持函数调用的模型,推荐使用`gpt-3.5-turbo-16k` -- 使用此功能可能会造成难以预期的账号余额消耗,请关注 -- [逆向库插件](https://github.com/RockChinQ/revLibs)现在也支持函数调用了..您可以在完全免费的情况下使用GPT-3.5进行函数调用,若您在主程序配置了内容函数并启用,逆向ChatGPT会自动使用这些函数 - -### ?QChatGPT有什么类型的插件?区别是什么? - -QChatGPT具有`行为插件`和`内容函数`两种扩展方式,行为插件是完整的插件结构,是由运行期间的事件驱动的,内容函数被包含于一个完整的插件体中,由GPT接口驱动。 - -> 还是不理解?可以尝试根据插件开发页的步骤自行编写插件 - -## QChatGPT的一些不错的内容函数插件 - -- [WebwlkrPlugin](https://github.com/RockChinQ/WebwlkrPlugin) - 让机器人能联网!! diff --git "a/res/wiki/7-\346\217\222\344\273\266\345\274\200\345\217\221.md" "b/res/wiki/7-\346\217\222\344\273\266\345\274\200\345\217\221.md" deleted file mode 100644 index c9cd2657..00000000 --- "a/res/wiki/7-\346\217\222\344\273\266\345\274\200\345\217\221.md" +++ /dev/null @@ -1,481 +0,0 @@ -> [!WARNING] -> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) - -QChatGPT 插件开发Wiki - -> 请先阅读[插件使用页](https://github.com/RockChinQ/QChatGPT/wiki/5-%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8) -> 请先阅读[技术信息页](https://github.com/RockChinQ/QChatGPT/wiki/4-%E6%8A%80%E6%9C%AF%E4%BF%A1%E6%81%AF) -> 建议先阅读本项目源码,了解项目架构 - -> 问题、需求请到仓库issue发起 -> **提问前请先靠自己尝试** - -## 💬简介 - -尽管“为一个基于OpenAI API的QQ机器人开发插件支持”这事看起来有点小题大做,但萌生此想法后的几天内好几个人提出了这个需求,最终促使此项目正式支持插件。 - -## 🧱实现 - -基于`importlib`库加载模块的方法动态加载额外Python程序文件以便实现插件加载,插件均存放在`plugins`文件夹,其中的所有`.py`文件都将被加载(除了所有`__init__.py`) - -## 📚示例代码 - -请查看代码目录`tests/plugin_examples`中的插件目录 - -## 💻快速开始 - -按照文档部署此项目,并使其正常运行。 -在`plugins`目录下新建目录`hello`,在其中新建空文件`__init__.py`以标记此目录为软件包,继续新建文件`main.py`。 - -> 您也可以使用[hello_plugin](https://github.com/RockChinQ/hello_plugin)作为模板直接生成插件代码仓库 - -编辑`main.py`输入以下内容: - -```Python -from pkg.plugin.models import * -from pkg.plugin.host import EventContext, PluginHost - -""" -在收到私聊或群聊消息"hello"时,回复"hello, <发送者id>!"或"hello, everyone!" -""" - - -# 注册插件 -@register(name="Hello", description="hello world", version="0.1", author="RockChinQ") -class HelloPlugin(Plugin): - - # 插件加载时触发 - # plugin_host (pkg.plugin.host.PluginHost) 提供了与主程序交互的一些方法,详细请查看其源码 - def __init__(self, plugin_host: PluginHost): - pass - - # 当收到个人消息时触发 - @on(PersonNormalMessageReceived) - def person_normal_message_received(self, event: EventContext, **kwargs): - msg = kwargs['text_message'] - if msg == "hello": # 如果消息为hello - - # 输出调试信息 - logging.debug("hello, {}".format(kwargs['sender_id'])) - - # 回复消息 "hello, <发送者id>!" - event.add_return("reply", ["hello, {}!".format(kwargs['sender_id'])]) - - # 阻止该事件默认行为(向接口获取回复) - event.prevent_default() - - # 当收到群消息时触发 - @on(GroupNormalMessageReceived) - def group_normal_message_received(self, event: EventContext, **kwargs): - msg = kwargs['text_message'] - if msg == "hello": # 如果消息为hello - - # 输出调试信息 - logging.debug("hello, {}".format(kwargs['sender_id'])) - - # 回复消息 "hello, everyone!" - event.add_return("reply", ["hello, everyone!"]) - - # 阻止该事件默认行为(向接口获取回复) - event.prevent_default() - - # 插件卸载时触发 - def __del__(self): - pass - -``` - -此插件将实现:私聊收到`hello`消息时回复`hello, <发送者QQ号>!`,群聊收到`hello`消息时回复`hello, everyone!` - -### 解读此插件程序 - -- `import`从`pkg.plugin`引入`models`模块的所有字段(此程序使用了其中的`register函数`、`on函数`、`Plugin类`、`PersonNormalMessageReceived事件`、`GroupNormalMessageReceived事件`) -- `@register()`将类`HelloPlugin`标记为一个插件类,声明插件名称为`Hello`以及插件简介、版本、作者 -- 声明类`HelloPlugin`继承于`Plugin`,此类可以随意命名,插件名称只与`register`调用时的参数有关 -- 声明此类的`__init__`方法,此方法是可选的,其中的代码将在主程序启动时加载插件的时候被执行 -- `@on`将方法`person_normal_message_received`标记为一个事件处理器,处理`PersonNormalMessageReceived`(收到私聊消息并在获取OpenAI回复前触发)事件,此方法可以随意命名,绑定的事件只与`on`中的参数有关,更多支持的事件可到`pkg.plugin.models.py`文件中查看或查看下方`API`节 -- 输出调试信息,程序中可通过`logging`将日志输出到控制台和`qchatgpt.log`文件 -- 方法内部从参数中取出`text_message`参数,判断是否为`hello`,如果是就将返回值`reply`设置为`["hello, {}!".format(kwargs['sender_id'])]`,接下来调用`event`对象的`prevent_default`方法,阻止原程序默认行为 - - 每个事件`提供的参数`和`支持的返回值`请查看`pkg.plugin.models`中的每个事件的注释或查看下方`API`节 - - `event`对象提供的方法请查看`pkg.plugin.host`中的`EventContext`类或查看下方`API`节 -- 用相似的程序注册`GroupNormalMessageReceived`事件处理群消息 - -编写完毕保存后,重新启动主程序,查看到输出中包含以下内容,即为加载成功: - -``` -[2023-01-16 18:29:47.193] host.py (43) - [INFO] : 加载模块: hello.main -[2023-01-16 18:29:47.194] models.py (209) - [INFO] : 插件注册完成: n='Hello', d='hello world', v=0.1, a='RockChinQ' () -``` - -> 建议在`config.py`中设置`logging_level = logging.DEBUG`以便开启调试输出 - -## ❗规范(重要) - -- 请每个插件独立一个目录以便管理,建议在Github上创建一个仓库储存单个插件,以便获取和更新 -- 插件名使用`大驼峰命名法`,如`Hello`、`ExamplePlugin`、`ChineseCommands`等 -- 一个目录内可以存放多个Python程序文件,以独立出插件的各个功能,便于开发者管理,但不建议在一个目录内注册多个插件 -- 插件需要的依赖库请在插件目录下的`requirements.txt`中指定,程序从储存库获取此插件时将自动安装依赖 - -## 🪝内容函数 - -通过[GPT的Function Calling能力](https://platform.openai.com/docs/guides/gpt/function-calling)实现的`内容函数`,这是一种嵌入对话中,由GPT自动调用的函数。 - -> 您的插件不一定必须包含内容函数,请先查看内容函数页了解此功能 - -
-示例:联网插件 - -加载含有联网功能的内容函数的插件[WebwlkrPlugin](https://github.com/RockChinQ/WebwlkrPlugin),向机器人询问在线内容 - -``` -# 控制台输出 -[2023-07-29 17:37:18.698] message.py (26) - [INFO] : [person_1010553892]发送消息:介绍一下这个项目:https://git... -[2023-07-29 17:37:21.292] util.py (67) - [INFO] : message='OpenAI API response' path=https://api.openai.com/v1/chat/completions processing_ms=1902 request_id=941afc13b2e1bba1e7877b92a970cdea response_code=200 -[2023-07-29 17:37:21.293] chat_completion.py (159) - [INFO] : 执行函数调用: name=Webwlkr-access_the_web, arguments={'url': 'https://github.com/RockChinQ/QChatGPT', 'brief_len': 512} -[2023-07-29 17:37:21.848] chat_completion.py (164) - [INFO] : 函数执行完成。 -``` - -![Webwlkr插件](https://github.com/RockChinQ/QChatGPT/blob/master/res/screenshots/webwlkr_plugin.png?raw=true) - -
- -### 内容函数编写步骤 - -1️⃣ 请先按照上方步骤编写您的插件基础结构,现在请删除(当然你也可以不删,只是为了简洁)上述插件内容的诸个由`@on`装饰的类函数 - -
-删除后的结构 - -```python -from pkg.plugin.models import * -from pkg.plugin.host import EventContext, PluginHost - -""" -在收到私聊或群聊消息"hello"时,回复"hello, <发送者id>!"或"hello, everyone!" -""" - - -# 注册插件 -@register(name="Hello", description="hello world", version="0.1", author="RockChinQ") -class HelloPlugin(Plugin): - - # 插件加载时触发 - # plugin_host (pkg.plugin.host.PluginHost) 提供了与主程序交互的一些方法,详细请查看其源码 - def __init__(self, plugin_host: PluginHost): - pass - - # 插件卸载时触发 - def __del__(self): - pass -``` - -
- -2️⃣ 现在我们将以下函数添加到刚刚删除的函数的位置 - -```Python - -# 要添加的函数 - -@func(name="access_the_web") # 设置函数名称 -def _(url: str): - """Call this function to search about the question before you answer any questions. - - Do not search through baidu.com at any time. - - If you need to search somthing, visit https://www.google.com/search?q=xxx. - - If user ask you to open a url (start with http:// or https://), visit it directly. - - Summary the plain content result by yourself, DO NOT directly output anything in the result you got. - - Args: - url(str): url to visit - - Returns: - str: plain text content of the web page - """ - import requests - from bs4 import BeautifulSoup - # 你需要先使用 - # pip install beautifulsoup4 - # 安装依赖 - - r = requests.get( - url, - timeout=10, - headers={ - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183" - } - ) - soup = BeautifulSoup(r.text, 'html.parser') - - s = soup.get_text() - - # 删除多余的空行或仅有\t和空格的行 - s = re.sub(r'\n\s*\n', '\n', s) - - if len(s) >= 512: # 截取获取到的网页纯文本内容的前512个字 - return s[:512] - - return s - -``` -
-现在这个文件内容应该是这样 - -```python -from pkg.plugin.models import * -from pkg.plugin.host import EventContext, PluginHost - -""" -在收到私聊或群聊消息"hello"时,回复"hello, <发送者id>!"或"hello, everyone!" -""" - - -# 注册插件 -@register(name="Hello", description="hello world", version="0.1", author="RockChinQ") -class HelloPlugin(Plugin): - - # 插件加载时触发 - # plugin_host (pkg.plugin.host.PluginHost) 提供了与主程序交互的一些方法,详细请查看其源码 - def __init__(self, plugin_host: PluginHost): - pass - - @func(name="access_the_web") - def _(url: str): - """Call this function to search about the question before you answer any questions. - - Do not search through baidu.com at any time. - - If you need to search somthing, visit https://www.google.com/search?q=xxx. - - If user ask you to open a url (start with http:// or https://), visit it directly. - - Summary the plain content result by yourself, DO NOT directly output anything in the result you got. - - Args: - url(str): url to visit - - Returns: - str: plain text content of the web page - """ - import requests - from bs4 import BeautifulSoup - # 你需要先使用 - # pip install beautifulsoup4 - # 安装依赖 - - r = requests.get( - url, - timeout=10, - headers={ - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183" - } - ) - soup = BeautifulSoup(r.text, 'html.parser') - - s = soup.get_text() - - # 删除多余的空行或仅有\t和空格的行 - s = re.sub(r'\n\s*\n', '\n', s) - - if len(s) >= 512: # 截取获取到的网页纯文本内容的前512个字 - return s[:512] - - return s - - # 插件卸载时触发 - def __del__(self): - pass -``` - -
- -#### 请注意: - -- 函数的注释必须严格按照要求的格式进行书写,具体格式请查看[此文档](https://github.com/RockChinQ/CallingGPT/wiki/1.-Function-Format#function-format) -- 内容函数和`以@on装饰的行为函数`可以同时存在于同一个插件,并同时受到`switch.json`中的插件开关的控制 -- 务必确保您使用的模型支持函数调用功能,可以到`config.py`的`completion_api_params`中修改模型,推荐使用`gpt-3.5-turbo-16k` - -3️⃣ 现在您的程序已具备网络访问功能,重启程序,询问机器人有关在线的内容或直接发送文章链接请求其总结。 - -- 这仅仅是一个示例,需要更高效的网络访问能力支持插件,请查看[WebwlkrPlugin](https://github.com/RockChinQ/WebwlkrPlugin) - -## 🔒版本要求 - -若您的插件对主程序的版本有要求,可以使用以下函数进行断言,若不符合版本,此函数将报错并打断此函数所在的流程: - -```python -require_ver("v2.5.1") # 要求最低版本为 v2.5.1 -``` - -```python -require_ver("v2.5.1", "v2.6.0") # 要求最低版本为 v2.5.1, 同时要求最高版本为 v2.6.0 -``` - -- 此函数在主程序`v2.5.1`中加入 -- 此函数声明在`pkg.plugin.models`模块中,在插件示例代码最前方已引入此模块所有内容,故可直接使用 - -## 📄API参考 - -### 说明 - -> 下一版本将会添加统一的插件API,欢迎在[此讨论](https://github.com/RockChinQ/QChatGPT/discussions/637)回复您的需求! - -事件处理函数将会获得一系列参数,可以在`kwargs`中取出。 -其中`host`参数(`pkg.plugin.host.PluginHost`类的实例)是插件宿主,提供与主程序各个模块交互的一些方法。 -`event`参数(`pkg.plugin.host.EventContext`类的实例)是事件执行期间的上下文,提供对此次事件执行的一些操作方法。 - -事件返回值均为**可选**的,可以通过调用`event.add_return(key: str, ret)`来提交返回值 - -### 事件 - -所有事件参数均有`host`和`event`,以下仅展示其他参数 -关于`YiriMirai`支持的消息链组件,请查看 [YiriMirai的文档](https://yiri-mirai.wybxc.cc/docs/basic/message-chain) - -```Python -PersonMessageReceived = "person_message_received" -"""收到私聊消息时,在判断是否应该响应前触发 - kwargs: - launcher_type: str 发起对象类型(group/person) - launcher_id: int 发起对象ID(群号/QQ号) - sender_id: int 发送者ID(QQ号) - message_chain: mirai.models.message.MessageChain 消息链 -""" - -GroupMessageReceived = "group_message_received" -"""收到群聊消息时,在判断是否应该响应前触发(所有群消息) - kwargs: - launcher_type: str 发起对象类型(group/person) - launcher_id: int 发起对象ID(群号/QQ号) - sender_id: int 发送者ID(QQ号) - message_chain: mirai.models.message.MessageChain 消息链 -""" - -PersonNormalMessageReceived = "person_normal_message_received" -"""判断为应该处理的私聊普通消息时触发 - kwargs: - launcher_type: str 发起对象类型(group/person) - launcher_id: int 发起对象ID(群号/QQ号) - sender_id: int 发送者ID(QQ号) - text_message: str 消息文本 - - returns (optional): - alter: str 修改后的消息文本 - reply: list 回复消息组件列表,元素为YiriMirai支持的消息组件 -""" - -PersonCommandSent = "person_command_sent" -"""判断为应该处理的私聊命令时触发 - kwargs: - launcher_type: str 发起对象类型(group/person) - launcher_id: int 发起对象ID(群号/QQ号) - sender_id: int 发送者ID(QQ号) - command: str 命令 - params: list[str] 参数列表 - text_message: str 完整命令文本 - is_admin: bool 是否为管理员 - - returns (optional): - alter: str 修改后的完整命令文本 - reply: list 回复消息组件列表,元素为YiriMirai支持的消息组件 -""" - -GroupNormalMessageReceived = "group_normal_message_received" -"""判断为应该处理的群聊普通消息时触发 - kwargs: - launcher_type: str 发起对象类型(group/person) - launcher_id: int 发起对象ID(群号/QQ号) - sender_id: int 发送者ID(QQ号) - text_message: str 消息文本 - - returns (optional): - alter: str 修改后的消息文本 - reply: list 回复消息组件列表,元素为YiriMirai支持的消息组件 -""" - -GroupCommandSent = "group_command_sent" -"""判断为应该处理的群聊命令时触发 - kwargs: - launcher_type: str 发起对象类型(group/person) - launcher_id: int 发起对象ID(群号/QQ号) - sender_id: int 发送者ID(QQ号) - command: str 命令 - params: list[str] 参数列表 - text_message: str 完整命令文本 - is_admin: bool 是否为管理员 - - returns (optional): - alter: str 修改后的完整命令文本 - reply: list 回复消息组件列表,元素为YiriMirai支持的消息组件 -""" - -NormalMessageResponded = "normal_message_responded" -"""获取到对普通消息的文字响应时触发 - kwargs: - launcher_type: str 发起对象类型(group/person) - launcher_id: int 发起对象ID(群号/QQ号) - sender_id: int 发送者ID(QQ号) - session: pkg.openai.session.Session 会话对象 - prefix: str 回复文字消息的前缀 - response_text: str 响应文本 - finish_reason: str 响应结束原因 - - returns (optional): - prefix: str 修改后的回复文字消息的前缀 - reply: list 替换回复消息组件列表 -""" - -SessionFirstMessageReceived = "session_first_message_received" -"""会话被第一次交互时触发 - kwargs: - session_name: str 会话名称(_) - session: pkg.openai.session.Session 会话对象 - default_prompt: str 预设值 -""" - -SessionExplicitReset = "session_reset" -"""会话被用户手动重置时触发,此事件不支持阻止默认行为 - kwargs: - session_name: str 会话名称(_) - session: pkg.openai.session.Session 会话对象 -""" - -SessionExpired = "session_expired" -"""会话过期时触发 - kwargs: - session_name: str 会话名称(_) - session: pkg.openai.session.Session 会话对象 - session_expire_time: int 已设置的会话过期时间(秒) -""" - -KeyExceeded = "key_exceeded" -"""api-key超额时触发 - kwargs: - key_name: str 超额的api-key名称 - usage: dict 超额的api-key使用情况 - exceeded_keys: list[str] 超额的api-key列表 -""" - -KeySwitched = "key_switched" -"""api-key超额切换成功时触发,此事件不支持阻止默认行为 - kwargs: - key_name: str 切换成功的api-key名称 - key_list: list[str] api-key列表 -""" - -PromptPreProcessing = "prompt_pre_processing" # 于v2.5.1加入 -"""每回合调用接口前对prompt进行预处理时触发,此事件不支持阻止默认行为 - kwargs: - session_name: str 会话名称(_) - default_prompt: list 此session使用的情景预设内容 - prompt: list 此session现有的prompt内容 - text_message: str 用户发送的消息文本 - - returns (optional): - default_prompt: list 修改后的情景预设内容 - prompt: list 修改后的prompt内容 - text_message: str 修改后的消息文本 -""" -``` - -### host: PluginHost 详解 - -提供与主程序各个模块交互的一些方法,具体查看`pkg.plugin.host`中的`PluginHost`类 - -### event: EventContext 详解 - -提供对此次事件执行的一些操作方法,具体查看`pkg.plugin.host`中的`EventContext`类 diff --git "a/res/wiki/8-\345\256\230\346\226\271\346\216\245\345\217\243\343\200\201ChatGPT\347\275\221\351\241\265\347\211\210\343\200\201ChatGPT-API\345\214\272\345\210\253.md" "b/res/wiki/8-\345\256\230\346\226\271\346\216\245\345\217\243\343\200\201ChatGPT\347\275\221\351\241\265\347\211\210\343\200\201ChatGPT-API\345\214\272\345\210\253.md" deleted file mode 100644 index 64bcc8d0..00000000 --- "a/res/wiki/8-\345\256\230\346\226\271\346\216\245\345\217\243\343\200\201ChatGPT\347\275\221\351\241\265\347\211\210\343\200\201ChatGPT-API\345\214\272\345\210\253.md" +++ /dev/null @@ -1,17 +0,0 @@ -> [!WARNING] -> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) - -## 多个对话接口有何区别? - -出于对稳定性的高要求,本项目主线接入的是GPT-3模型接口,此接口由OpenAI官方开放,稳定性强。 -目前支持通过加载[插件](https://github.com/RockChinQ/revLibs)的方式接入ChatGPT网页版,使用的是acheong08/ChatGPT的逆向工程库,但文本生成质量更高。 -同时,程序主线已支持ChatGPT API,并作为默认接口 [#195](https://github.com/RockChinQ/QChatGPT/issues/195) - -|官方接口|ChatGPT网页版|ChatGPT API -|---|---|---| -|官方开放,稳定性高 | 由[acheong08](https://github.com/acheong08)破解网页版协议接入| 由OpenAI官方开放 -|一次性回复,响应速度较快| 流式回复,响应速度较慢|响应速度较快| -|收费,0.02美元/千字|免费|收费,0.002美元/千字| -|GPT-3模型|GPT-3.5模型|GPT-3.5模型| -|任何地区主机均可使用(疑似受到GFW影响)|ChatGPT限制访问的区域使用有难度|任何地区主机均可使用(疑似受到GFW影响)| - diff --git "a/res/wiki/9-go-cqhttp\351\205\215\347\275\256.md" "b/res/wiki/9-go-cqhttp\351\205\215\347\275\256.md" deleted file mode 100644 index f8aad243..00000000 --- "a/res/wiki/9-go-cqhttp\351\205\215\347\275\256.md" +++ /dev/null @@ -1,73 +0,0 @@ -> [!WARNING] -> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) - -# 配置go-cqhttp用于登录QQ - -> 若您是从旧版本升级到此版本以使用go-cqhttp的用户,请您按照`config-template.py`的内容修改`config.py`,添加`msg_source_adapter`配置项并将其设为`nakuru`,同时添加`nakuru_config`字段按照说明配置。 - -## 步骤 - -1. 从[go-cqhttp的Release](https://github.com/Mrs4s/go-cqhttp/releases/latest)下载最新的go-cqhttp可执行文件(建议直接下载可执行文件压缩包,而不是安装器) -2. 解压并运行,首次运行会询问需要开放的网络协议,**请填入`02`并回车,必须输入`02`❗❗❗❗❗❗❗** - -

你这里必须得输入`02`,你懂么,`0`必须得输入,看好了,看好下面输入什么了吗?别他妈的搁那就输个`2`完了启动连不上还跑群里问,问一个我踢一个。

- -``` -C:\Softwares\go-cqhttp.old> .\go-cqhttp.exe -未找到配置文件,正在为您生成配置文件中! -请选择你需要的通信方式: -> 0: HTTP通信 -> 1: 云函数服务 -> 2: 正向 Websocket 通信 -> 3: 反向 Websocket 通信 -请输入你需要的编号(0-9),可输入多个,同一编号也可输入多个(如: 233) -您的选择是:02 -``` - -提示已生成`config.yml`文件,关闭go-cqhttp。 - -3. 打开go-cqhttp同目录的`config.yml` - - 1. 编辑账号登录信息 - - 只需要修改下方`uin`和`password`为你要登录的机器人账号的QQ号和密码即可。 - **若您不填写,将会在启动时请求扫码登录。** - - ```yaml - account: # 账号相关 - uin: 1233456 # QQ账号 - password: '' # 密码为空时使用扫码登录 - encrypt: false # 是否开启密码加密 - status: 0 # 在线状态 请参考 https://docs.go-cqhttp.org/guide/config.html#在线状态 - relogin: # 重连设置 - delay: 3 # 首次重连延迟, 单位秒 - interval: 3 # 重连间隔 - max-times: 0 # 最大重连次数, 0为无限制 - ``` - - 2. 修改websocket端口 - - 在`config.yml`下方找到以下内容 - - ```yaml - - ws: - # 正向WS服务器监听地址 - address: 0.0.0.0:8080 - middlewares: - <<: *default # 引用默认中间件 - ``` - - **将`0.0.0.0:8080`改为`0.0.0.0:6700`**,保存并关闭`config.yml`。 - - 3. 若您的服务器位于公网,强烈建议您填写`access-token` (可选) - - ```yaml - # 默认中间件锚点 - default-middlewares: &default - # 访问密钥, 强烈推荐在公网的服务器设置 - access-token: '' - ``` - -4. 配置完成,重新启动go-cqhttp - -> 若启动后登录不成功,请尝试根据[此文档](https://docs.go-cqhttp.org/guide/config.html#%E8%AE%BE%E5%A4%87%E4%BF%A1%E6%81%AF)修改`device.json`的协议编号。 diff --git a/res/wiki/Home.md b/res/wiki/Home.md deleted file mode 100644 index 1d111c04..00000000 --- a/res/wiki/Home.md +++ /dev/null @@ -1,30 +0,0 @@ -> [!WARNING] -> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) - -欢迎查看QChatGPT的Wiki页。 - -## 简介 - -调用OpenAI官方提供的API接口,结合mirai和YiriMirai框架,将QQ消息与语言模型连接,实现更加智能的对话机器人 - -## 技术栈 - -- [Mirai](https://github.com/mamoe/mirai) 高效率 QQ 机器人支持库 -- [YiriMirai](https://github.com/YiriMiraiProject/YiriMirai) 一个轻量级、低耦合的基于 mirai-api-http 的 Python SDK。 -- [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) cqhttp的golang实现,轻量、原生跨平台. -- [nakuru-project](https://github.com/Lxns-Network/nakuru-project) - 一款为 go-cqhttp 的正向 WebSocket 设计的 Python SDK,支持纯 CQ 码与消息链的转换处理 -- [nakuru-project-idk](https://github.com/idoknow/nakuru-project-idk) - 由idoknow维护的nakuru-project分支 -- [dulwich](https://github.com/jelmer/dulwich) Pure-Python Git implementation -- [OpenAI API](https://openai.com/api/) OpenAI API - -## 代码结构 - -- `pkg.database` 数据库操作相关 - - 数据库用于存放会话的历史记录,确保在程序重启后能记住对话内容 -- `pkg.openai` OpenAI API相关 - - 用于调用OpenAI的API生成回复内容 -- `pkg.qqbot` QQ机器人相关 - - 处理QQ收到的消息,调用API并进行回复 -- `pkg.utils` 常用功能包 -- `pkg.audit` 审计模块 -- `pkg.plugin` 插件管理相关功能 diff --git a/templates/platform.json b/templates/platform.json index b048f2f8..ae39b207 100644 --- a/templates/platform.json +++ b/templates/platform.json @@ -1,13 +1,5 @@ { "platform-adapters": [ - { - "adapter": "yiri-mirai", - "enable": false, - "host": "127.0.0.1", - "port": 8080, - "verifyKey": "yirimirai", - "qq": 123456789 - }, { "adapter": "nakuru", "enable": false, @@ -20,7 +12,7 @@ "adapter": "aiocqhttp", "enable": false, "host": "0.0.0.0", - "port": 8080, + "port": 2280, "access-token": "" }, { @@ -37,7 +29,10 @@ "track-function-calls": true, "quote-origin": false, "at-sender": false, - "force-delay": [0, 0], + "force-delay": { + "min": 0, + "max": 0 + }, "long-text-process": { "threshold": 256, "strategy": "forward", diff --git a/templates/schema/command.json b/templates/schema/command.json new file mode 100644 index 00000000..1cfc2541 --- /dev/null +++ b/templates/schema/command.json @@ -0,0 +1,39 @@ +{ + "type": "object", + "layout": "expansion-panels", + "properties": { + "command-prefix": { + "type": "array", + "title": "命令前缀", + "description": "以数组形式设置,程序将前缀符合设置的消息视为命令(群内需要符合群响应规则)", + "items": { + "type": "string" + }, + "default": [ + "!", + "!" + ] + }, + "privilege": { + "type": "object", + "title": "权限管理", + "description": "设置每个命令的权限配置。普通用户权限级别为 1,管理员(system.json中设置的)权限级别为 2;在这里设置每个命令的最低权限级别,若设置为1,则用户和管理员均可用,若为2,则仅管理员可用;设置子命令时,以点号间隔,如\"plugin.on\"", + "properties": { + "placeholder": { + "type": "integer", + "minimum": 1, + "maximum": 2, + "const": 1 + } + }, + "patternProperties": { + "^[a-zA-Z0-9_.]+$": { + "type": "integer", + "minimum": 1, + "maximum": 2 + } + }, + "default": {} + } + } +} \ No newline at end of file diff --git a/templates/schema/pipeline.json b/templates/schema/pipeline.json new file mode 100644 index 00000000..3a51aeba --- /dev/null +++ b/templates/schema/pipeline.json @@ -0,0 +1,326 @@ +{ + "type": "object", + "layout": "expansion-panels", + "properties": { + "access-control": { + "type": "object", + "title": "访问控制", + "properties": { + "mode": { + "type": "string", + "title": "访问控制模式", + "description": "访问控制模式,支持黑名单和白名单", + "enum": [ + "blacklist", + "whitelist" + ], + "default": "blacklist" + }, + "blacklist": { + "type": "array", + "title": "黑名单", + "description": "黑名单中的会话将无法使用机器人,仅在访问控制模式为黑名单时有效。格式:{type}_{id},示例:group_12345678 或 person_12341234", + "items": { + "type": "string", + "format": "regex", + "pattern": "^(person|group)_(\\d)*$" + }, + "default": [] + }, + "whitelist": { + "type": "array", + "title": "白名单", + "description": "仅白名单中的会话可以使用机器人,仅在访问控制模式为白名单时有效。格式:{type}_{id},示例:group_12345678 或 person_12341234", + "items": { + "type": "string", + "format": "regex", + "pattern": "^(person|group)_(\\d)*$" + }, + "default": [] + } + }, + "required": [ + "mode" + ] + }, + "respond-rules": { + "type": "object", + "title": "群消息响应规则", + "description": "仅处理 访问控制 允许的会话的消息。所有未指定的群使用 默认响应规则,若需指定特定的群的规则,请输入 群号 并添加,并设置响应规则", + "properties": { + "default": { + "type": "object", + "title": "默认响应规则", + "properties": { + "at": { + "type": "boolean", + "title": "是否响应 @ 消息", + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + } + }, + "prefix": { + "type": "array", + "title": "响应前缀", + "description": "带有指定前缀的消息即使没有 at 机器人也会被响应,发送给 AI 时会删除前缀", + "items": { + "type": "string" + }, + "default": [] + }, + "regexp": { + "type": "array", + "title": "响应正则表达式", + "description": "正则表达式教程:https://www.runoob.com/regexp/regexp-syntax.html", + "items": { + "type": "string", + "format": "regex" + }, + "default": [] + }, + "random": { + "type": "number", + "title": "随机响应概率", + "description": "数值范围是0.0-1.0,对应概率0%-100%,为1.0时所有消息都响应", + "minimum": 0, + "maximum": 1, + "step": 0.01, + "layout": { + "comp": "slider", + "props": { + "color": "primary" + } + } + } + } + } + }, + "patternProperties": { + "^\\d+$": { + "type": "object", + "properties": { + "at": { + "type": "boolean", + "title": "是否响应 @ 消息", + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + } + }, + "prefix": { + "type": "array", + "title": "响应前缀", + "description": "带有指定前缀的消息即使没有 at 机器人也会被响应,发送给 AI 时会删除前缀", + "items": { + "type": "string" + }, + "default": [] + }, + "regexp": { + "type": "array", + "title": "响应正则表达式", + "description": "正则表达式教程:https://www.runoob.com/regexp/regexp-syntax.html", + "items": { + "type": "string", + "format": "regex" + }, + "default": [] + }, + "random": { + "type": "number", + "title": "随机响应概率", + "description": "数值范围是0.0-1.0,对应概率0%-100%,为1.0时所有消息都响应", + "minimum": 0, + "maximum": 1, + "step": 0.01, + "layout": { + "comp": "slider", + "props": { + "color": "primary" + } + } + } + } + } + } + }, + "income-msg-check": { + "type": "boolean", + "title": "检查传入消息内容", + "description": "是否对传入的消息(用户消息)进行检查,需配合审核策略使用(AI 响应内容一定会通过检查策略)", + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + } + }, + "ignore-rules": { + "type": "object", + "title": "传入消息忽略规则", + "description": "符合规则的传入消息将被忽略,仅传入消息检查被启用时生效", + "properties": { + "prefix": { + "type": "array", + "title": "忽略前缀", + "description": "具有指定前缀的消息将被忽略", + "items": { + "type": "string" + }, + "default": [] + }, + "regexp": { + "type": "array", + "title": "忽略正则表达式", + "description": "正则表达式教程:https://www.runoob.com/regexp/regexp-syntax.html", + "items": { + "type": "string", + "format": "regex" + }, + "default": [] + } + } + }, + "check-sensitive-words": { + "type": "boolean", + "title": "本地敏感词检查", + "description": "是否启用本地敏感词检查", + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + } + }, + "baidu-cloud-examine": { + "type": "object", + "title": "百度云内容审核配置", + "description": "百度云内容审核配置,前往:https://cloud.baidu.com/doc/ANTIPORN/index.html 获取 API Key 和 API Secret", + "properties": { + "enable": { + "type": "boolean", + "title": "是否启用", + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + } + }, + "api-key": { + "type": "string", + "title": "API Key", + "default": "" + }, + "api-secret": { + "type": "string", + "title": "API Secret", + "default": "" + } + } + }, + "rate-limit": { + "type": "object", + "title": "请求限速规则", + "properties": { + "strategy": { + "type": "string", + "title": "限速策略", + "description": "会话中的请求速率超过限制时的处理策略,drop为丢弃新请求,wait为等待请求速率降到限制以下", + "enum": [ + "drop", + "wait" + ], + "default": "drop" + }, + "algo": { + "type": "string", + "title": "限速算法", + "description": "目前仅支持 fixwin(固定窗口),支持插件扩展", + "enum": [ + "fixwin" + ], + "default": "fixwin" + }, + "fixwin": { + "type": "object", + "title": "固定窗口限速策略配置", + "description": "所有会话使用默认限速策略,若需指定特定会话的限速策略,请输入 会话名称(格式为 {type}_{id},示例:group_123456 或 person_123456) 并添加,以设置特定会话的限速参数", + "properties": { + "default": { + "type": "object", + "title": "默认限速策略", + "properties": { + "window-size": { + "type": "integer", + "title": "窗口大小(秒)", + "minimum": 1, + "default": 60 + }, + "limit": { + "type": "integer", + "title": "窗口期间允许的最大消息数", + "minimum": 1, + "default": 60 + } + } + } + }, + "patternProperties": { + "^(person|group).*$": { + "type": "object", + "title": "会话限速", + "properties": { + "window-size": { + "type": "integer", + "title": "窗口大小(秒)", + "minimum": 1, + "default": 60 + }, + "limit": { + "type": "integer", + "title": "窗口期间允许的最大消息数", + "minimum": 1, + "default": 60 + } + } + } + } + } + } + }, + "msg-truncate": { + "type": "object", + "title": "对话历史记录截断", + "description": "将在发送消息给模型之前对当前会话的历史消息进行截断,以限制传给模型的消息长度", + "properties": { + "method": { + "type": "string", + "title": "截断方法", + "description": "目前仅支持 round(按回合截断),支持插件扩展", + "enum": [ + "round" + ], + "default": "round" + }, + "round": { + "type": "object", + "title": "轮次截断策略配置", + "properties": { + "max-round": { + "type": "integer", + "title": "最大保留前文回合数", + "minimum": 1, + "default": 10 + } + } + } + } + } + } +} \ No newline at end of file diff --git a/templates/schema/platform.json b/templates/schema/platform.json new file mode 100644 index 00000000..074c9ae2 --- /dev/null +++ b/templates/schema/platform.json @@ -0,0 +1,221 @@ +{ + "type": "object", + "layout": "expansion-panels", + "properties": { + "platform-adapters": { + "type": "array", + "title": "消息平台适配器", + "default": {}, + "items": { + "type": "object", + "oneOf": [ + { + "title": "Nakuru 适配器", + "description": "用于接入 go-cqhttp", + "properties": { + "adapter": { + "type": "string", + "const": "nakuru" + }, + "enable": { + "type": "boolean", + "default": false, + "description": "是否启用此适配器", + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + } + }, + "host": { + "type": "string", + "default": "127.0.0.1" + }, + "ws_port": { + "type": "integer", + "default": 8080 + }, + "http_port": { + "type": "integer", + "default": 5700 + }, + "token": { + "type": "string", + "default": "" + } + } + }, + { + "title": "aiocqhttp 适配器", + "description": "用于接入 Lagrange 等兼容 OneBot v11 协议的机器人框架(仅支持反向ws)", + "properties": { + "adapter": { + "type": "string", + "const": "aiocqhttp" + }, + "enable": { + "type": "boolean", + "default": false, + "description": "是否启用此适配器", + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + } + }, + "host": { + "type": "string", + "default": "0.0.0.0", + "description": "监听的 IP 地址,一般就保持 0.0.0.0 就可以了。使用 aiocqhttp 时,LangBot 作为服务端被动等待框架连接,请在 Lagrange 等框架中设置被动 ws 地址或者反向 ws 地址(具体视框架而定)为 LangBot 监听的地址,且路径为/ws,例如:ws://127.0.0.1:2280/ws" + }, + "port": { + "type": "integer", + "default": 2290, + "description": "设置监听的端口,默认2280,需在 Lagrange 等框架中设置为与此处一致的端口" + }, + "access-token": { + "type": "string", + "default": "", + "description": "设置访问密钥,与 Lagrange 等框架中设置的保持一致" + } + } + }, + { + "title": "qq-botpy 适配器", + "description": "用于接入 QQ 官方机器人 API", + "properties": { + "adapter": { + "type": "string", + "const": "qq-botpy" + }, + "enable": { + "type": "boolean", + "default": false, + "description": "是否启用此适配器", + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + } + }, + "appid": { + "type": "string", + "default": "", + "description": "申请到的QQ官方机器人的appid" + }, + "secret": { + "type": "string", + "default": "", + "description": "申请到的QQ官方机器人的secret" + }, + "intents": { + "type": "array", + "description": "控制监听的事件类型,需要填写才能接收到对应消息,目前支持的事件类型有:public_guild_messages(QQ 频道消息)、direct_message(QQ 频道私聊消息)、public_messages(QQ 群 和 列表私聊消息)", + "default": [ + "public_guild_messages", + "direct_message", + "public_messages" + ] + } + } + } + ] + } + }, + "track-function-calls": { + "type": "boolean", + "default": true, + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + }, + "title": "跟踪内容函数调用", + "description": "开启之后,在对话中调用的内容函数记录也会发给用户,关闭后(false)仅会发给用户最终结果" + }, + "quote-origin": { + "type": "boolean", + "default": false, + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + }, + "title": "引用原消息", + "description": "在群内回复时是否引用原消息" + }, + "at-sender": { + "type": "boolean", + "default": false, + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + }, + "title": "是否 at 原用户", + "description": "在群内回复时是否@发送者" + }, + "force-delay": { + "type": "object", + "default": { + "min": 0, + "max": 0 + }, + "title": "强制消息延迟范围", + "description": "在将响应内容发回给用户前的强制消息随机延迟时间范围,以防风控,单位是秒", + "properties": { + "min": { + "type": "integer", + "default": 0, + "description": "最小值,单位是秒" + }, + "max": { + "type": "integer", + "default": 0, + "description": "最大值,单位是秒" + } + } + }, + "long-text-process": { + "type": "object", + "title": "长消息处理策略", + "properties": { + "threshold": { + "type": "integer", + "default": 256, + "title": "长消息处理阈值", + "description": "当消息长度超过此阈值时,将启用长消息处理策略" + }, + "strategy": { + "type": "string", + "default": "forward", + "title": "长消息处理策略", + "description": "长消息处理策略,目前支持forward(转发消息组件)和image(文字转图片)。aiocqhttp 和 qq-botpy 不支持 forward 策略" + }, + "font-path": { + "type": "string", + "description": "image的渲染字体。未设置时,如果在windows下,会尝试寻找系统的微软雅黑字体,若找不到,则转为forward策略。未设置时,若不是windows系统,则直接转为forward策略", + "default": "" + } + } + }, + "hide-exception-info": { + "type": "boolean", + "default": true, + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + }, + "title": "向用户隐藏AI接口的异常信息", + "description": "是否向用户隐藏AI的异常信息,如果为true,当请求AI接口出现异常时,会返回一个错误的提示给用户。而把报错详情输出在控制台。" + } + } +} \ No newline at end of file diff --git a/templates/schema/provider.json b/templates/schema/provider.json new file mode 100644 index 00000000..6b214233 --- /dev/null +++ b/templates/schema/provider.json @@ -0,0 +1,207 @@ +{ + "type": "object", + "layout": "expansion-panels", + "properties": { + "enable-chat": { + "type": "boolean", + "default": true, + "title": "启用聊天功能", + "description": "是否启用 AI 聊天功能" + }, + "enable-vision": { + "type": "boolean", + "default": true, + "title": "启用视觉功能", + "description": "是否开启AI视觉功能。需要使用的模型同时支持视觉功能,详情见元数据板块" + }, + "keys": { + "type": "object", + "title": "模型接口密钥", + "description": "以字典的形式设置若干个密钥组,每个密钥组的键为密钥组名称,值为密钥列表。模型与密钥组的对应关系,请查看元数据板块", + "properties": { + "openai": { + "type": "array", + "title": "OpenAI API 密钥", + "description": "OpenAI API 密钥", + "items": { + "type": "string" + }, + "default": [] + }, + "anthropic": { + "type": "array", + "title": "Anthropic API 密钥", + "description": "Anthropic API 密钥", + "items": { + "type": "string" + }, + "default": [] + }, + "moonshot": { + "type": "array", + "title": "Moonshot API 密钥", + "description": "Moonshot API 密钥", + "items": { + "type": "string" + }, + "default": [] + }, + "deepseek": { + "type": "array", + "title": "DeepSeek API 密钥", + "description": "DeepSeek API 密钥", + "items": { + "type": "string" + }, + "default": [] + } + } + }, + "requester": { + "type": "object", + "title": "大模型请求器", + "description": "以字典的形式设置若干个请求器,每个请求器的键为请求器名称,值为请求器配置。模型与请求器的对应关系,请查看元数据板块。实现请求器的方式,请查看插件编写教程", + "properties": { + "openai-chat-completions": { + "type": "object", + "title": "OpenAI API 请求配置", + "description": "仅可编辑 URL 和 超时时间,额外请求参数不支持可视化编辑,请到编辑器编辑", + "properties": { + "base-url": { + "type": "string", + "title": "API URL" + }, + "args": { + "type": "object", + "default": {} + }, + "timeout": { + "type": "number", + "title": "API 请求超时时间", + "default": 120 + } + } + }, + "anthropic-messages": { + "type": "object", + "title": "Anthropic API 请求配置", + "description": "仅可编辑 URL 和 超时时间,额外请求参数不支持可视化编辑,请到编辑器编辑", + "properties": { + "base-url": { + "type": "string", + "title": "API URL" + }, + "args": { + "type": "object", + "default": {} + }, + "timeout": { + "type": "number", + "title": "API 请求超时时间", + "default": 120 + } + } + }, + "moonshot-chat-completions": { + "type": "object", + "title": "Moonshot API 请求配置", + "description": "仅可编辑 URL 和 超时时间,额外请求参数不支持可视化编辑,请到编辑器编辑", + "properties": { + "base-url": { + "type": "string", + "title": "API URL" + }, + "args": { + "type": "object", + "default": {} + }, + "timeout": { + "type": "number", + "title": "API 请求超时时间", + "default": 120 + } + } + }, + "deepseek-chat-completions": { + "type": "object", + "title": "DeepSeek API 请求配置", + "description": "仅可编辑 URL 和 超时时间,额外请求参数不支持可视化编辑,请到编辑器编辑", + "properties": { + "base-url": { + "type": "string", + "title": "API URL" + }, + "args": { + "type": "object", + "default": {} + }, + "timeout": { + "type": "number", + "title": "API 请求超时时间", + "default": 120 + } + } + }, + "ollama-chat": { + "type": "object", + "title": "Ollama API 请求配置", + "description": "仅可编辑 URL 和 超时时间,额外请求参数不支持可视化编辑,请到编辑器编辑", + "properties": { + "base-url": { + "type": "string", + "title": "API URL" + }, + "args": { + "type": "object" + }, + "timeout": { + "type": "number", + "title": "API 请求超时时间", + "default": 600 + } + } + } + } + }, + "model": { + "type": "string", + "title": "所使用的模型名称", + "description": "设置要使用的模型名称。通常来说直接填写模型名称即可,但如果要使用原生接口不是 ChatCompletion 但以 ChatCompletion 接口格式接入的模型,请在模型名称前方加一个 OneAPI/ 前缀以进行区分。 简单来说可以认为是:现阶段非 OpenAI 的模型接入都需要在模型名称前方加一个 OneAPI/ 前缀。\n\n例如:\n\n1. 通过 OneAPI 等中转服务接入了 OpenAI 的 gpt-4 模型,由于 gpt-4 也是使用 ChatCompletion 接口格式进行请求,则可以直接填入 gpt-4;\n2. 通过 OneAPI 等中转服务接入了 Google 的 gemini-pro 模型,由于 gemini-pro 原生接口格式并非 ChatCompletion,因此需要填入 OneAPI/gemini-pro。\n具体支持的模型列表和各个模型对应的请求器和密钥组,请查看元数据板块 llm-models.json " + }, + "prompt-mode": { + "type": "string", + "title": "情景预设(人格)模式", + "description": "值为normal(单预设模式)和full-scenario(完整历史对话模式);normal模式时,使用下方设置的情景预设,也支持读取data/prompts目录下的文件内容作为单个 System Prompt,文件名即为prompt的名称;full-scenario模式时,读取 data/scenario/ 下的完整历史对话作为情景预设", + "enum": ["normal", "full-scenario"], + "default": "normal" + }, + "prompt": { + "type": "object", + "title": "情景预设(人格)", + "description": "设置情景预设(人格)。值为空字符串时,将不使用情景预设(人格)。normal模式时,使用下方设置的情景预设,也支持读取data/prompts目录下的文件内容作为单个 System Prompt,文件名即为prompt的名称;full-scenario模式时,读取 data/scenario/ 下的完整历史对话作为情景预设", + "properties": { + "default": { + "type": "string", + "title": "默认情景预设", + "description": "设置默认情景预设。值为空字符串时,将不使用情景预设(人格)", + "default": "" + } + }, + "patternProperties": { + "^.*$": { + "type": "string", + "title": "情景预设", + "description": "设置情景预设。值为空字符串时,将不使用情景预设(人格)", + "default": "" + } + }, + "required": ["default"] + }, + "runner": { + "type": "string", + "title": "请求运行器", + "description": "设置请求运行器。值为local-agent时,使用内置默认运行器;支持插件扩展", + "default": "local-agent" + } + } +} \ No newline at end of file diff --git a/templates/schema/system.json b/templates/schema/system.json new file mode 100644 index 00000000..c2da4232 --- /dev/null +++ b/templates/schema/system.json @@ -0,0 +1,126 @@ +{ + "type": "object", + "layout": "expansion-panels", + "properties": { + "admin-sessions": { + "type": "array", + "title": "管理员会话", + "description": "设置管理员会话,格式为 {type}_{id},type 为 \"group\" 或 \"person\",如:group_123456 或 person_123456", + "items": { + "type": "string", + "format": "regex", + "pattern": "^(person|group)_(\\d+)$" + }, + "default": [] + }, + "network-proxies": { + "type": "object", + "title": "网络代理", + "description": "正向代理,http和https都要填,例如:http://127.0.0.1:7890 https://127.0.0.1:7890 。不使用代理请留空。正向代理也可以用环境变量设置:http_proxy 和 https_proxy", + "properties": { + "http": { + "type": "string" + }, + "https": { + "type": "string" + } + } + }, + "report-usage": { + "type": "boolean", + "title": "上报遥测数据", + "description": "遥测数据用于统计和分析项目使用情况,不包含任何隐私信息,不建议禁用", + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + } + }, + "logging-level": { + "type": "string", + "title": "日志等级", + "description": "目前无效,启用调试模式请设置环境变量:export DEBUG=true" + }, + "session-concurrency": { + "type": "object", + "title": "会话消息处理并发数", + "description": "粒度是单个会话,所有会话使用默认并发数,若需指定特定会话的并发数,请输入 会话名称(格式为 {type}_{id},示例:group_123456 或 person_123456) 并添加,以设置特定会话的并发数", + "properties": { + "default": { + "type": "integer" + } + }, + "patternProperties": { + "^(person|group)_(\\d+)$": { + "type": "integer" + } + } + }, + "pipeline-concurrency": { + "type": "integer", + "title": "流水线消息处理并发数", + "description": "粒度是整个程序,目前使用 FCFS 算法调度各个请求" + }, + "qcg-center-url": { + "type": "string", + "title": "遥测服务器地址", + "description": "运行期间推送遥测数据的目标地址,默认为官方地址,若您自己部署了 https://github.com/RockChinQ/qcg-center,可以改为你的地址。" + }, + "help-message": { + "type": "string", + "title": "帮助消息", + "description": "用户发送 !help 命令时的输出", + "layout": "textarea" + }, + "http-api": { + "type": "object", + "title": "HTTP 接口", + "properties": { + "enable": { + "type": "boolean", + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + }, + "title": "是否启用" + }, + "host": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "jwt-expire": { + "type": "integer", + "title": "JWT 过期时间", + "description": "单位:秒" + } + } + }, + "persistence": { + "type": "object", + "title": "持久化设置", + "properties": { + "sqlite": { + "type": "object", + "title": "sqlite", + "properties": { + "path": { + "type": "string" + } + } + }, + "use": { + "type": "string", + "title": "所使用的数据库", + "enum": [ + "sqlite" + ] + } + } + } + } +} \ No newline at end of file diff --git a/templates/system.json b/templates/system.json index 0f6669fe..c090ea0e 100644 --- a/templates/system.json +++ b/templates/system.json @@ -11,5 +11,17 @@ }, "pipeline-concurrency": 20, "qcg-center-url": "https://api.qchatgpt.rockchin.top/api/v2", - "help-message": "QChatGPT - 😎高稳定性、🧩支持插件、🌏实时联网的 ChatGPT QQ 机器人🤖\n链接:https://q.rkcn.top" + "help-message": "LangBot - 😎高稳定性、🧩支持插件、🌏实时联网的 ChatGPT QQ 机器人🤖\n链接:https://q.rkcn.top", + "http-api": { + "enable": true, + "host": "0.0.0.0", + "port": 5300, + "jwt-expire": 604800 + }, + "persistence": { + "sqlite": { + "path": "data/persistence.db" + }, + "use": "sqlite" + } } \ No newline at end of file diff --git a/web/.browserslistrc b/web/.browserslistrc new file mode 100644 index 00000000..dc3bc09a --- /dev/null +++ b/web/.browserslistrc @@ -0,0 +1,4 @@ +> 1% +last 2 versions +not dead +not ie 11 diff --git a/web/.editorconfig b/web/.editorconfig new file mode 100644 index 00000000..7053c49a --- /dev/null +++ b/web/.editorconfig @@ -0,0 +1,5 @@ +[*.{js,jsx,ts,tsx,vue}] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/web/.eslintrc.js b/web/.eslintrc.js new file mode 100644 index 00000000..6e7e1b14 --- /dev/null +++ b/web/.eslintrc.js @@ -0,0 +1,10 @@ +module.exports = { + root: true, + env: { + node: true, + }, + extends: [ + 'plugin:vue/vue3-essential', + 'eslint:recommended', + ], +} diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 00000000..11f5d714 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,22 @@ +.DS_Store +node_modules +/dist + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/web/README.md b/web/README.md new file mode 100644 index 00000000..280a55a2 --- /dev/null +++ b/web/README.md @@ -0,0 +1 @@ +# WebUI diff --git a/web/index.html b/web/index.html new file mode 100644 index 00000000..150c3979 --- /dev/null +++ b/web/index.html @@ -0,0 +1,16 @@ + + + + + + + + LangBot 面板 + + + +
+ + + + diff --git a/web/jsconfig.json b/web/jsconfig.json new file mode 100644 index 00000000..dad0634c --- /dev/null +++ b/web/jsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowJs": true, + "target": "es5", + "module": "esnext", + "baseUrl": "./", + "moduleResolution": "bundler", + "paths": { + "@/*": [ + "src/*" + ] + }, + "lib": [ + "esnext", + "dom", + "dom.iterable", + "scripthost" + ] + } +} diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 00000000..d692c64b --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,6363 @@ +{ + "name": "web", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "web", + "version": "0.0.0", + "dependencies": { + "@koumoul/vjsf": "^3.0.0-beta.46", + "@mdi/font": "7.4.47", + "ajv": "^8.17.1", + "ajv-dist": "^8.17.1", + "ajv-errors": "^3.0.0", + "ajv-formats": "^3.0.1", + "ajv-i18n": "^4.2.0", + "ansi_up": "^6.0.2", + "axios": "^1.7.7", + "codemirror": "^5.65.18", + "core-js": "^3.37.1", + "json-editor-vue": "^0.17.3", + "roboto-fontface": "*", + "vue": "^3.4.31", + "vuedraggable": "^4.1.0", + "vuetify": "^3.6.11", + "vuex": "^4.0.2" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.5", + "eslint": "^8.57.0", + "eslint-config-standard": "^17.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.4.0", + "eslint-plugin-vue": "^9.27.0", + "sass": "1.77.6", + "unplugin-fonts": "^1.1.1", + "unplugin-vue-components": "^0.27.2", + "unplugin-vue-router": "^0.10.0", + "vite": "^5.3.3", + "vite-plugin-vuetify": "^2.0.3", + "vue-router": "^4.4.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.2.tgz", + "integrity": "sha512-wJGylKtMFR/Ds6Gh01+OovXE/pncPiKZNNBKuC39pKnH+XK5d9+WsNqcrdxPjFPFTigRBqse0rfxw9UxrfyhPg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + }, + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.1.tgz", + "integrity": "sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", + "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.3.tgz", + "integrity": "sha512-kDqEU5sCP55Oabl6E7m5N+vZRoc0iWqgDVhEKifcHzPzjqCegcO4amfrYVL9PmPZpl4G0yjkpTpUO/Ui8CzO8A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.2.tgz", + "integrity": "sha512-PDFG5DjHxSEjOXk9TQYYVjZDqlZTFaDBfhQixHnQOEVDDNHUbEh/hstAjcQJaA6FQdZTD1hquXTK0rVBLADR1g==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.7.tgz", + "integrity": "sha512-6+iLsXvITWKHYlkgHPCs/qiX4dNzn8N78YfhOFvPtPYCkuXqZq10rAfsUMhOq7O/1VjJqdXRflyExlfVcu/9VQ==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", + "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==", + "license": "MIT" + }, + "node_modules/@codemirror/view": { + "version": "6.34.2", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.34.2.tgz", + "integrity": "sha512-d6n0WFvL970A9Z+l9N2dO+Hk9ev4hDYQzIx+B9tCyBP0W5wPEszi1rhuyFesNSkLZzXbQE5FPH7F/z/TMJfoPA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.4.0", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", + "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.6.0.tgz", + "integrity": "sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", + "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@json-layout/core": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@json-layout/core/-/core-0.32.1.tgz", + "integrity": "sha512-/x+D8epj48MKPlDTKE7lgwjd6ThFF99+fZwxwuActV3XAFwE55bu0sK45i9yDoTkgEHHWCDlxlOdDkJP0hRQ/g==", + "license": "MIT", + "dependencies": { + "@json-layout/vocabulary": "^0.23.2", + "@types/markdown-it": "^13.0.1", + "ajv": "^8.12.0", + "ajv-errors": "^3.0.0", + "ajv-formats": "^2.1.1", + "ajv-i18n": "^4.2.0", + "debug": "^4.3.4", + "immer": "^10.0.3", + "magicast": "^0.3.3", + "markdown-it": "^13.0.2" + } + }, + "node_modules/@json-layout/core/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@json-layout/vocabulary": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/@json-layout/vocabulary/-/vocabulary-0.23.2.tgz", + "integrity": "sha512-CDQ/nFZmcMdhn0Ud/f5Q3IoRemQQdw2CPm5pufRqo27T71JhIw12KluVW1jsZWlwK3v7q7yqOoVS8Ax9bUOQ4w==", + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-errors": "^3.0.0", + "ajv-formats": "^2.1.1", + "debug": "^4.3.4" + } + }, + "node_modules/@json-layout/vocabulary/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@jsonquerylang/jsonquery": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jsonquerylang/jsonquery/-/jsonquery-3.1.1.tgz", + "integrity": "sha512-P6Qo5egd3W8TBpqQsqaZtZ9lPO7oXBM21QdkYamCAYZHv9VCPXiI8NeIuSoXdoe5zKVZPUWmqaI14uacJLmcNw==", + "license": "ISC", + "bin": { + "jsonquery": "bin/cli.js" + } + }, + "node_modules/@koumoul/vjsf": { + "version": "3.0.0-beta.46", + "resolved": "https://registry.npmjs.org/@koumoul/vjsf/-/vjsf-3.0.0-beta.46.tgz", + "integrity": "sha512-dp9EuyZrZNRHb5+8eLMHWNEI9HPhpLoeOWzDWj43tE+162mGmhi42SqVMS5fbx73ZHF2FHe4jZszgdj0MiYB2A==", + "license": "MIT", + "dependencies": { + "@json-layout/core": "0.32.1", + "@vueuse/core": "^10.5.0", + "debug": "^4.3.4", + "ejs": "^3.1.9" + }, + "peerDependencies": { + "vue": "^3.4.3", + "vuetify": "^3.6.13" + } + }, + "node_modules/@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz", + "integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@mdi/font": { + "version": "7.4.47", + "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.4.47.tgz", + "integrity": "sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==", + "license": "Apache-2.0" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz", + "integrity": "sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.0", + "@parcel/watcher-darwin-arm64": "2.5.0", + "@parcel/watcher-darwin-x64": "2.5.0", + "@parcel/watcher-freebsd-x64": "2.5.0", + "@parcel/watcher-linux-arm-glibc": "2.5.0", + "@parcel/watcher-linux-arm-musl": "2.5.0", + "@parcel/watcher-linux-arm64-glibc": "2.5.0", + "@parcel/watcher-linux-arm64-musl": "2.5.0", + "@parcel/watcher-linux-x64-glibc": "2.5.0", + "@parcel/watcher-linux-x64-musl": "2.5.0", + "@parcel/watcher-win32-arm64": "2.5.0", + "@parcel/watcher-win32-ia32": "2.5.0", + "@parcel/watcher-win32-x64": "2.5.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz", + "integrity": "sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz", + "integrity": "sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz", + "integrity": "sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz", + "integrity": "sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz", + "integrity": "sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz", + "integrity": "sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz", + "integrity": "sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz", + "integrity": "sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz", + "integrity": "sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz", + "integrity": "sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz", + "integrity": "sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz", + "integrity": "sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz", + "integrity": "sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@replit/codemirror-indentation-markers": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/@replit/codemirror-indentation-markers/-/codemirror-indentation-markers-6.5.3.tgz", + "integrity": "sha512-hL5Sfvw3C1vgg7GolLe/uxX5T3tmgOA3ZzqlMv47zjU1ON51pzNWiVbS22oh6crYhtVhv8b3gdXwoYp++2ilHw==", + "license": "MIT", + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.2.tgz", + "integrity": "sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "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/rollup-android-arm-eabi": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz", + "integrity": "sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz", + "integrity": "sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz", + "integrity": "sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz", + "integrity": "sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz", + "integrity": "sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz", + "integrity": "sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz", + "integrity": "sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz", + "integrity": "sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz", + "integrity": "sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz", + "integrity": "sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz", + "integrity": "sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz", + "integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz", + "integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz", + "integrity": "sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz", + "integrity": "sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz", + "integrity": "sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sphinxxxx/color-conversion": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@sphinxxxx/color-conversion/-/color-conversion-2.2.2.tgz", + "integrity": "sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw==", + "license": "ISC" + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/linkify-it": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", + "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "13.0.9", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.9.tgz", + "integrity": "sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==", + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^3", + "@types/mdurl": "^1" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", + "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz", + "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue-macros/common": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-1.14.0.tgz", + "integrity": "sha512-xwQhDoEXRNXobNQmdqOD20yUGdVLVLZe4zhDlT9q/E+z+mvT3wukaAoJG80XRnv/BcgOOCVpxqpkQZ3sNTgjWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.6", + "@rollup/pluginutils": "^5.1.0", + "@vue/compiler-sfc": "^3.5.4", + "ast-kit": "^1.1.0", + "local-pkg": "^0.5.0", + "magic-string-ast": "^0.6.2" + }, + "engines": { + "node": ">=16.14.0" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.2.25" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.10.tgz", + "integrity": "sha512-iXWlk+Cg/ag7gLvY0SfVucU8Kh2CjysYZjhhP70w9qI4MvSox4frrP+vDGvtQuzIcgD8+sxM6lZvCtdxGunTAA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.10", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.10.tgz", + "integrity": "sha512-DyxHC6qPcktwYGKOIy3XqnHRrrXyWR2u91AjP+nLkADko380srsC2DC3s7Y1Rk6YfOlxOlvEQKa9XXmLI+W4ZA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.10", + "@vue/shared": "3.5.10" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.10.tgz", + "integrity": "sha512-to8E1BgpakV7224ZCm8gz1ZRSyjNCAWEplwFMWKlzCdP9DkMKhRRwt0WkCjY7jkzi/Vz3xgbpeig5Pnbly4Tow==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.10", + "@vue/compiler-dom": "3.5.10", + "@vue/compiler-ssr": "3.5.10", + "@vue/shared": "3.5.10", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.47", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.10.tgz", + "integrity": "sha512-hxP4Y3KImqdtyUKXDRSxKSRkSm1H9fCvhojEYrnaoWhE4w/y8vwWhnosJoPPe2AXm5sU7CSbYYAgkt2ZPhDz+A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.10", + "@vue/shared": "3.5.10" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.10.tgz", + "integrity": "sha512-kW08v06F6xPSHhid9DJ9YjOGmwNDOsJJQk0ax21wKaUYzzuJGEuoKNU2Ujux8FLMrP7CFJJKsHhXN9l2WOVi2g==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.10" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.10.tgz", + "integrity": "sha512-9Q86I5Qq3swSkFfzrZ+iqEy7Vla325M7S7xc1NwKnRm/qoi1Dauz0rT6mTMmscqx4qz0EDJ1wjB+A36k7rl8mA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.10", + "@vue/shared": "3.5.10" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.10.tgz", + "integrity": "sha512-t3x7ht5qF8ZRi1H4fZqFzyY2j+GTMTDxRheT+i8M9Ph0oepUxoadmbwlFwMoW7RYCpNQLpP2Yx3feKs+fyBdpA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.10", + "@vue/runtime-core": "3.5.10", + "@vue/shared": "3.5.10", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.10.tgz", + "integrity": "sha512-IVE97tt2kGKwHNq9yVO0xdh1IvYfZCShvDSy46JIh5OQxP1/EXSpoDqetVmyIzL7CYOWnnmMkVqd7YK2QSWkdw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.10", + "@vue/shared": "3.5.10" + }, + "peerDependencies": { + "vue": "3.5.10" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.10.tgz", + "integrity": "sha512-VkkBhU97Ki+XJ0xvl4C9YJsIZ2uIlQ7HqPpZOS3m9VCvmROPaChZU6DexdMJqvz9tbgG+4EtFVrSuailUq5KGQ==", + "license": "MIT" + }, + "node_modules/@vuetify/loader-shared": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-2.0.3.tgz", + "integrity": "sha512-Ss3GC7eJYkp2SF6xVzsT7FAruEmdihmn4OCk2+UocREerlXKWgOKKzTN5PN3ZVN5q05jHHrsNhTuWbhN61Bpdg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "upath": "^2.0.1" + }, + "peerDependencies": { + "vue": "^3.0.0", + "vuetify": "^3.0.0" + } + }, + "node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-typescript": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz", + "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", + "license": "MIT", + "peerDependencies": { + "acorn": ">=8.9.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "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/ajv-dist": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv-dist/-/ajv-dist-8.17.1.tgz", + "integrity": "sha512-KzJwANMzTTR/RERGnkx+bHzmxIfMTPMMv7+cH1d6Lx9UQ7BZyhiieq4hnO5lRuBWOtYTUL8hyWs7RJYI/45Rtg==", + "license": "MIT" + }, + "node_modules/ajv-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", + "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.0.1" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-i18n": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ajv-i18n/-/ajv-i18n-4.2.0.tgz", + "integrity": "sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg==", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.0.0-beta.0" + } + }, + "node_modules/ansi_up": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ansi_up/-/ansi_up-6.0.2.tgz", + "integrity": "sha512-3G3vKvl1ilEp7J1u6BmULpMA0xVoW/f4Ekqhl8RTrJrhEBkonKn5k3bUc5Xt+qDayA6iDX0jyUh3AbZjB/l0tw==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-kit": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-1.2.1.tgz", + "integrity": "sha512-h31wotR7rkFLrlmGPn0kGqOZ/n5EQFvp7dBs400chpHDhHc8BK3gpvyHDluRujuGgeoTAv3dSIMz9BI3JxAWyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.6", + "pathe": "^1.1.2" + }, + "engines": { + "node": ">=16.14.0" + } + }, + "node_modules/ast-walker-scope": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.6.2.tgz", + "integrity": "sha512-1UWOyC50xI3QZkRuDj6PqDtpm1oHWtYs+NQGwqL/2R11eN3Q81PHAHPM0SWW3BNQm53UDwS//Jv8L4CCVLM1bQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "ast-kit": "^1.0.1" + }, + "engines": { + "node": ">=16.14.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/builtins": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/builtins/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/codemirror": { + "version": "5.65.18", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.18.tgz", + "integrity": "sha512-Gaz4gHnkbHMGgahNt3CA5HBk5lLQBqmD/pBgeB4kQU6OedZmqMBjlRF0LSrp2tJ4wlLNPm2FfaUd1pDy0mdlpA==", + "license": "MIT" + }, + "node_modules/codemirror-wrapped-line-indent": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/codemirror-wrapped-line-indent/-/codemirror-wrapped-line-indent-1.0.8.tgz", + "integrity": "sha512-5UwuHCz4oAZuvot1DbfFxSxJacTESdNGa/KpJD7HfpVpDAJdgB1vV9OG4b4pkJqPWuOfIpFLTQEKS85kTpV+XA==", + "license": "MIT", + "peerDependencies": { + "@codemirror/language": "^6.9.0", + "@codemirror/state": "^6.2.1", + "@codemirror/view": "^6.17.1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", + "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz", + "integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "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-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "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.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-compat-utils/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-config-standard": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", + "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-es-x": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/ota-meshi", + "https://opencollective.com/eslint" + ], + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", + "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.9.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-n": { + "version": "16.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz", + "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "builtins": "^5.0.1", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "globals": "^13.24.0", + "ignore": "^5.2.4", + "is-builtin-module": "^3.2.1", + "is-core-module": "^2.12.1", + "minimatch": "^3.1.2", + "resolve": "^1.22.2", + "semver": "^7.5.3" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", + "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.28.0.tgz", + "integrity": "sha512-ShrihdjIhOTxs+MfWun6oJWuk+g/LAhN+CiuOl/jjkG3l0F2AuK5NMTaWqyvBgkFtpYmyks6P4603mLmhNJW8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "vue-eslint-parser": "^9.4.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-vue/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/esm-env": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.1.4.tgz", + "integrity": "sha512-oO82nKPHKkzIj/hbtuDYy/JHqBHFlMIW36SDiPCVsj87ntDLcWN+sJ1erdVryd4NxODacFTsdrIE3b7IamqbOg==", + "license": "MIT" + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrap": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz", + "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz", + "integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "license": "MIT" + }, + "node_modules/immutable-json-patch": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/immutable-json-patch/-/immutable-json-patch-6.0.1.tgz", + "integrity": "sha512-BHL/cXMjwFZlTOffiWNdY8ZTvNyYLrutCnWxrcKPHr5FqpAb6vsO6WWSPnVSys3+DruFN6lhHJJPHi8uELQL5g==", + "license": "ISC" + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "license": "MIT", + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-reference": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", + "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "license": "Apache-2.0", + "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/jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-editor-vue": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/json-editor-vue/-/json-editor-vue-0.17.3.tgz", + "integrity": "sha512-MVpD3TInIlruq9ye/J3XmYHTH+pqfyW0E1GVUV2ug5M0X/19zGslJ+FgeikDflvTVUxhVuCEnc9spMYmPj5Lyw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "vanilla-jsoneditor": "^2.0.0", + "vue-demi": "^0.14.10" + }, + "peerDependencies": { + "@vue/composition-api": ">=1", + "vue": "2||3" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/json-editor-vue/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/json-source-map/-/json-source-map-0.6.1.tgz", + "integrity": "sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonpath-plus": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.1.0.tgz", + "integrity": "sha512-gHfV1IYqH8uJHYVTs8BJX1XKy2/rR93+f8QQi0xhx95aCiXn1ettYAd5T+7FU6wfqyDoX/wy0pm/fL3jOKJ9Lg==", + "license": "MIT", + "dependencies": { + "@jsep-plugin/assignment": "^1.2.1", + "@jsep-plugin/regex": "^1.0.3", + "jsep": "^1.3.9" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/jsonrepair": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.10.0.tgz", + "integrity": "sha512-0Ex64Exiw0rPUEcSbhPN0ae4/5D0DZLIob9yagAF1OG5iU0mP+/t7q4gcxtQdn6i7FuQy2J/w1XbOdu/uhGV0w==", + "license": "ISC", + "bin": { + "jsonrepair": "bin/cli.js" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "license": "MIT", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/magic-string-ast": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-0.6.2.tgz", + "integrity": "sha512-oN3Bcd7ZVt+0VGEs7402qR/tjgjbM7kPlH/z7ufJnzTLVBzXJITRHOJiwMmmYMgZfdoWQsfQcY+iKlxiBppnMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.10" + }, + "engines": { + "node": ">=16.14.0" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/markdown-it": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz", + "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "license": "MIT" + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mlly": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", + "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.1.1", + "ufo": "^1.5.3" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "license": "MIT", + "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/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.0.tgz", + "integrity": "sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.7", + "mlly": "^1.7.1", + "pathe": "^1.1.2" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/roboto-fontface": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz", + "integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g==", + "license": "Apache-2.0" + }, + "node_modules/rollup": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.5.tgz", + "integrity": "sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.22.5", + "@rollup/rollup-android-arm64": "4.22.5", + "@rollup/rollup-darwin-arm64": "4.22.5", + "@rollup/rollup-darwin-x64": "4.22.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.5", + "@rollup/rollup-linux-arm-musleabihf": "4.22.5", + "@rollup/rollup-linux-arm64-gnu": "4.22.5", + "@rollup/rollup-linux-arm64-musl": "4.22.5", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.5", + "@rollup/rollup-linux-riscv64-gnu": "4.22.5", + "@rollup/rollup-linux-s390x-gnu": "4.22.5", + "@rollup/rollup-linux-x64-gnu": "4.22.5", + "@rollup/rollup-linux-x64-musl": "4.22.5", + "@rollup/rollup-win32-arm64-msvc": "4.22.5", + "@rollup/rollup-win32-ia32-msvc": "4.22.5", + "@rollup/rollup-win32-x64-msvc": "4.22.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sass": { + "version": "1.77.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", + "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "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.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "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/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sortablejs": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", + "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "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/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svelte": { + "version": "5.1.13", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.1.13.tgz", + "integrity": "sha512-xVNk8yLsZNfkyqWzVg8+nfU9ewiSjVW0S4qyTxfKa6Y7P5ZBhA+LDsh2cHWIXJQMltikQAk6W3sqGdQZSH58PA==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@types/estree": "^1.0.5", + "acorn": "^8.12.1", + "acorn-typescript": "^1.4.13", + "aria-query": "^5.3.1", + "axobject-query": "^4.1.0", + "esm-env": "^1.0.0", + "esrap": "^1.2.2", + "is-reference": "^3.0.2", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "license": "MIT" + }, + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unplugin": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.14.1.tgz", + "integrity": "sha512-lBlHbfSFPToDYp9pjXlUEFVxYLaue9f9T1HC+4OHlmj+HnMDdz9oZY+erXfoCe/5V/7gKUSY2jpXPb9S7f0f/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.12.1", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "webpack-sources": "^3" + }, + "peerDependenciesMeta": { + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/unplugin-fonts": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unplugin-fonts/-/unplugin-fonts-1.1.1.tgz", + "integrity": "sha512-/Aw/rL9D2aslGGM0vi+2R2aG508RSwawLnnBuo+JDSqYc4cHJO1R1phllhN6GysEhBp/6a4B6+vSFPVapWyAAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.12", + "unplugin": "^1.3.1" + }, + "peerDependencies": { + "@nuxt/kit": "^3.0.0", + "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.27.4.tgz", + "integrity": "sha512-1XVl5iXG7P1UrOMnaj2ogYa5YTq8aoh5jwDPQhemwO/OrXW+lPQKDXd1hMz15qxQPxgb/XXlbgo3HQ2rLEbmXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.0", + "chokidar": "^3.6.0", + "debug": "^4.3.6", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.11", + "minimatch": "^9.0.5", + "mlly": "^1.7.1", + "unplugin": "^1.12.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/unplugin-vue-components/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/unplugin-vue-router": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/unplugin-vue-router/-/unplugin-vue-router-0.10.8.tgz", + "integrity": "sha512-xi+eLweYAqolIoTRSmumbi6Yx0z5M0PLvl+NFNVWHJgmE2ByJG1SZbrn+TqyuDtIyln20KKgq8tqmL7aLoiFjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.4", + "@rollup/pluginutils": "^5.1.0", + "@vue-macros/common": "^1.12.2", + "ast-walker-scope": "^0.6.2", + "chokidar": "^3.6.0", + "fast-glob": "^3.3.2", + "json5": "^2.2.3", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.11", + "mlly": "^1.7.1", + "pathe": "^1.1.2", + "scule": "^1.3.0", + "unplugin": "^1.12.2", + "yaml": "^2.5.0" + }, + "peerDependencies": { + "vue-router": "^4.4.0" + }, + "peerDependenciesMeta": { + "vue-router": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-router/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vanilla-jsoneditor": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vanilla-jsoneditor/-/vanilla-jsoneditor-2.0.2.tgz", + "integrity": "sha512-/qsSp2B/sQsyW7SO8wq7vvEeq4Wqs5JT1j5SSfqlID7CPB9S95+vhzUjrKMpUk0YtxsySlxDecAcXK5Lzg22Sw==", + "license": "ISC", + "dependencies": { + "@codemirror/autocomplete": "^6.18.1", + "@codemirror/commands": "^6.7.1", + "@codemirror/lang-json": "^6.0.1", + "@codemirror/language": "^6.10.3", + "@codemirror/lint": "^6.8.2", + "@codemirror/search": "^6.5.6", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.34.1", + "@fortawesome/free-regular-svg-icons": "^6.6.0", + "@fortawesome/free-solid-svg-icons": "^6.6.0", + "@jsonquerylang/jsonquery": "^3.1.1", + "@lezer/highlight": "^1.2.1", + "@replit/codemirror-indentation-markers": "^6.5.3", + "ajv": "^8.17.1", + "codemirror-wrapped-line-indent": "^1.0.8", + "diff-sequences": "^29.6.3", + "immutable-json-patch": "^6.0.1", + "jmespath": "^0.16.0", + "json-source-map": "^0.6.1", + "jsonpath-plus": "^9.0.0 || ^10.1.0", + "jsonrepair": "^3.0.0", + "lodash-es": "^4.17.21", + "memoize-one": "^6.0.0", + "natural-compare-lite": "^1.4.0", + "sass": "^1.80.4", + "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0", + "vanilla-picker": "^2.12.3" + } + }, + "node_modules/vanilla-jsoneditor/node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/vanilla-jsoneditor/node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "license": "MIT", + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/vanilla-jsoneditor/node_modules/sass": { + "version": "1.80.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.6.tgz", + "integrity": "sha512-ccZgdHNiBF1NHBsWvacvT5rju3y1d/Eu+8Ex6c21nHp2lZGLBEtuwc415QfiI1PJa1TpCo3iXwwSRjRpn2Ckjg==", + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/vanilla-picker": { + "version": "2.12.3", + "resolved": "https://registry.npmjs.org/vanilla-picker/-/vanilla-picker-2.12.3.tgz", + "integrity": "sha512-qVkT1E7yMbUsB2mmJNFmaXMWE2hF8ffqzMMwe9zdAikd8u2VfnsVY2HQcOUi2F38bgbxzlJBEdS1UUhOXdF9GQ==", + "license": "ISC", + "dependencies": { + "@sphinxxxx/color-conversion": "^2.2.2" + } + }, + "node_modules/vite": { + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-vuetify": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vite-plugin-vuetify/-/vite-plugin-vuetify-2.0.4.tgz", + "integrity": "sha512-A4cliYUoP/u4AWSRVRvAPKgpgR987Pss7LpFa7s1GvOe8WjgDq92Rt3eVXrvgxGCWvZsPKziVqfHHdCMqeDhfw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@vuetify/loader-shared": "^2.0.3", + "debug": "^4.3.3", + "upath": "^2.0.1" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": ">=5", + "vue": "^3.0.0", + "vuetify": "^3.0.0" + } + }, + "node_modules/vue": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.10.tgz", + "integrity": "sha512-Vy2kmJwHPlouC/tSnIgXVg03SG+9wSqT1xu1Vehc+ChsXsRd7jLkKgMltVEFOzUdBr3uFwBCG+41LJtfAcBRng==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.10", + "@vue/compiler-sfc": "3.5.10", + "@vue/runtime-dom": "3.5.10", + "@vue/server-renderer": "3.5.10", + "@vue/shared": "3.5.10" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-eslint-parser": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", + "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vue-router": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz", + "integrity": "sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vuedraggable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz", + "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==", + "license": "MIT", + "dependencies": { + "sortablejs": "1.14.0" + }, + "peerDependencies": { + "vue": "^3.0.1" + } + }, + "node_modules/vuetify": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.2.tgz", + "integrity": "sha512-q0WTcRG977+a9Dqhb8TOaPm+Xmvj0oVhnBJhAdHWFSov3HhHTTxlH2nXP/GBTXZuuMHDbBeIWFuUR2/1Fx0PPw==", + "license": "MIT", + "engines": { + "node": "^12.20 || >=14.13" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/johnleider" + }, + "peerDependencies": { + "typescript": ">=4.7", + "vite-plugin-vuetify": ">=1.0.0", + "vue": "^3.3.0", + "webpack-plugin-vuetify": ">=2.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vite-plugin-vuetify": { + "optional": true + }, + "webpack-plugin-vuetify": { + "optional": true + } + } + }, + "node_modules/vuex": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz", + "integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.0.0-beta.11" + }, + "peerDependencies": { + "vue": "^3.0.2" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "license": "MIT" + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 00000000..fa14b869 --- /dev/null +++ b/web/package.json @@ -0,0 +1,46 @@ +{ + "name": "web", + "version": "0.0.0", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint . --fix --ignore-path .gitignore" + }, + "dependencies": { + "@koumoul/vjsf": "^3.0.0-beta.46", + "@mdi/font": "7.4.47", + "ajv": "^8.17.1", + "ajv-dist": "^8.17.1", + "ajv-errors": "^3.0.0", + "ajv-formats": "^3.0.1", + "ajv-i18n": "^4.2.0", + "ansi_up": "^6.0.2", + "axios": "^1.7.7", + "codemirror": "^5.65.18", + "core-js": "^3.37.1", + "json-editor-vue": "^0.17.3", + "roboto-fontface": "*", + "vue": "^3.4.31", + "vuedraggable": "^4.1.0", + "vuetify": "^3.6.11", + "vuex": "^4.0.2" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.5", + "eslint": "^8.57.0", + "eslint-config-standard": "^17.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.4.0", + "eslint-plugin-vue": "^9.27.0", + "sass": "1.77.6", + "unplugin-fonts": "^1.1.1", + "unplugin-vue-components": "^0.27.2", + "unplugin-vue-router": "^0.10.0", + "vite": "^5.3.3", + "vite-plugin-vuetify": "^2.0.3", + "vue-router": "^4.4.0" + } +} diff --git a/web/public/favicon.ico b/web/public/favicon.ico new file mode 100644 index 00000000..00a756f5 Binary files /dev/null and b/web/public/favicon.ico differ diff --git a/web/src/App.vue b/web/src/App.vue new file mode 100644 index 00000000..e1fb3b16 --- /dev/null +++ b/web/src/App.vue @@ -0,0 +1,322 @@ + + + + + diff --git a/web/src/assets/langbot-logo-block.png b/web/src/assets/langbot-logo-block.png new file mode 100644 index 00000000..b2caca7b Binary files /dev/null and b/web/src/assets/langbot-logo-block.png differ diff --git a/web/src/assets/langbot-logo.png b/web/src/assets/langbot-logo.png new file mode 100644 index 00000000..567a2e4f Binary files /dev/null and b/web/src/assets/langbot-logo.png differ diff --git a/web/src/components/AboutDialog.vue b/web/src/components/AboutDialog.vue new file mode 100644 index 00000000..c7f1037f --- /dev/null +++ b/web/src/components/AboutDialog.vue @@ -0,0 +1,90 @@ + + + + + \ No newline at end of file diff --git a/web/src/components/InitDialog.vue b/web/src/components/InitDialog.vue new file mode 100644 index 00000000..e25ff56d --- /dev/null +++ b/web/src/components/InitDialog.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/web/src/components/LoginDialog.vue b/web/src/components/LoginDialog.vue new file mode 100644 index 00000000..4cfa50a6 --- /dev/null +++ b/web/src/components/LoginDialog.vue @@ -0,0 +1,72 @@ + + + + + \ No newline at end of file diff --git a/web/src/components/NumberFieldData.vue b/web/src/components/NumberFieldData.vue new file mode 100644 index 00000000..fbdc86e7 --- /dev/null +++ b/web/src/components/NumberFieldData.vue @@ -0,0 +1,75 @@ + + + + + \ No newline at end of file diff --git a/web/src/components/PageTitle.vue b/web/src/components/PageTitle.vue new file mode 100644 index 00000000..a4cfb4f0 --- /dev/null +++ b/web/src/components/PageTitle.vue @@ -0,0 +1,51 @@ + + + + + \ No newline at end of file diff --git a/web/src/components/PluginCard.vue b/web/src/components/PluginCard.vue new file mode 100644 index 00000000..899e0329 --- /dev/null +++ b/web/src/components/PluginCard.vue @@ -0,0 +1,257 @@ + + + + + diff --git a/web/src/components/SettingWindow.vue b/web/src/components/SettingWindow.vue new file mode 100644 index 00000000..ff4f2735 --- /dev/null +++ b/web/src/components/SettingWindow.vue @@ -0,0 +1,287 @@ + + + + + \ No newline at end of file diff --git a/web/src/components/TaskCard.vue b/web/src/components/TaskCard.vue new file mode 100644 index 00000000..167f6f41 --- /dev/null +++ b/web/src/components/TaskCard.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/web/src/pages/Plugins.vue b/web/src/pages/Plugins.vue new file mode 100644 index 00000000..dbb7de99 --- /dev/null +++ b/web/src/pages/Plugins.vue @@ -0,0 +1,306 @@ + + + + + diff --git a/web/src/pages/Settings.vue b/web/src/pages/Settings.vue new file mode 100644 index 00000000..58a39699 --- /dev/null +++ b/web/src/pages/Settings.vue @@ -0,0 +1,89 @@ + + + + + \ No newline at end of file diff --git a/web/src/plugins/index.js b/web/src/plugins/index.js new file mode 100644 index 00000000..3ca203de --- /dev/null +++ b/web/src/plugins/index.js @@ -0,0 +1,31 @@ +/** + * plugins/index.js + * + * Automatically included in `./src/main.js` + */ + +// Plugins +import vuetify from './vuetify' +import router from '@/router' +import store from '@/store' +import axios from 'axios' + +export function registerPlugins (app) { + app + .use(vuetify) + .use(router) + .use(store) + + // 读取用户令牌 + const token = localStorage.getItem('user-token') + + if (token) { + store.state.user.jwtToken = token + } + + // 所有axios请求均携带用户令牌 + axios.defaults.headers.common['Authorization'] = `Bearer ${store.state.user.jwtToken}` + + app.config.globalProperties.$axios = axios + store.commit('initializeFetch') +} diff --git a/web/src/plugins/vuetify.js b/web/src/plugins/vuetify.js new file mode 100644 index 00000000..1db02919 --- /dev/null +++ b/web/src/plugins/vuetify.js @@ -0,0 +1,19 @@ +/** + * plugins/vuetify.js + * + * Framework documentation: https://vuetifyjs.com` + */ + +// Styles +import '@mdi/font/css/materialdesignicons.css' +import 'vuetify/styles' + +// Composables +import { createVuetify } from 'vuetify' + +// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides +export default createVuetify({ + theme: { + defaultTheme: 'light', + }, +}) diff --git a/web/src/router/index.js b/web/src/router/index.js new file mode 100644 index 00000000..b8572783 --- /dev/null +++ b/web/src/router/index.js @@ -0,0 +1,46 @@ + +/** + * router/index.ts + * + * Automatic routes for `./src/pages/*.vue` + */ + +// Composables +import { createRouter, createWebHashHistory } from 'vue-router/auto' +import DashBoard from '../pages/DashBoard.vue' +import Settings from '../pages/Settings.vue' +import Logs from '../pages/Logs.vue' +import Plugins from '../pages/Plugins.vue' + +const routes = [ + { path: '/', component: DashBoard }, + { path: '/settings', component: Settings }, + { path: '/logs', component: Logs }, + { path: '/plugins', component: Plugins }, +] + +const router = createRouter({ + history: createWebHashHistory(), + routes, +}) + +// Workaround for https://github.com/vitejs/vite/issues/11804 +router.onError((err, to) => { + if (err?.message?.includes?.('Failed to fetch dynamically imported module')) { + if (!localStorage.getItem('vuetify:dynamic-reload')) { + console.log('Reloading page to fix dynamic import error') + localStorage.setItem('vuetify:dynamic-reload', 'true') + location.assign(to.fullPath) + } else { + console.error('Dynamic import error, reloading page did not fix it', err) + } + } else { + console.error(err) + } +}) + +router.isReady().then(() => { + localStorage.removeItem('vuetify:dynamic-reload') +}) + +export default router diff --git a/web/src/store/index.js b/web/src/store/index.js new file mode 100644 index 00000000..64b126a3 --- /dev/null +++ b/web/src/store/index.js @@ -0,0 +1,42 @@ +import { createStore } from 'vuex' +import router from '@/router' +import axios from 'axios' + +export default createStore({ + state: { + // 开发时使用 + // apiBaseUrl: 'http://localhost:5300/api/v1', + apiBaseUrl: '/api/v1', + autoRefreshLog: false, + autoScrollLog: true, + settingsPageTab: '', + version: 'v0.0.0', + debug: false, + enabledPlatformCount: 0, + user: { + tokenChecked: false, + tokenValid: false, + systemInitialized: true, + jwtToken: '', + } + }, + mutations: { + initializeFetch() { + axios.defaults.baseURL = this.state.apiBaseUrl + + axios.get('/system/info').then(response => { + this.state.version = response.data.data.version + this.state.debug = response.data.data.debug + this.state.enabledPlatformCount = response.data.data.enabled_platform_count + }) + }, + fetchSystemInfo() { + axios.get('/system/info').then(response => { + this.state.version = response.data.data.version + this.state.debug = response.data.data.debug + this.state.enabledPlatformCount = response.data.data.enabled_platform_count + }) + } + }, + actions: {}, +}) diff --git a/web/src/styles/settings.scss b/web/src/styles/settings.scss new file mode 100644 index 00000000..3e36a279 --- /dev/null +++ b/web/src/styles/settings.scss @@ -0,0 +1,10 @@ +/** + * src/styles/settings.scss + * + * Configures SASS variables and Vuetify overwrites + */ + +// https://vuetifyjs.com/features/sass-variables/` +// @use 'vuetify/settings' with ( +// $color-pack: false +// ); diff --git a/web/vite.config.mjs b/web/vite.config.mjs new file mode 100644 index 00000000..31052bbe --- /dev/null +++ b/web/vite.config.mjs @@ -0,0 +1,64 @@ +// Plugins +import Components from 'unplugin-vue-components/vite' +import Vue from '@vitejs/plugin-vue' +import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify' +import ViteFonts from 'unplugin-fonts/vite' +import VueRouter from 'unplugin-vue-router/vite' + +import { commonjsDeps } from '@koumoul/vjsf/utils/build.js' + +// Utilities +import { defineConfig } from 'vite' +import { fileURLToPath, URL } from 'node:url' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + VueRouter(), + Vue({ + template: { transformAssetUrls } + }), + // https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme + Vuetify({ + autoImport: true, + styles: { + configFile: 'src/styles/settings.scss', + }, + }), + Components(), + ViteFonts({ + google: { + families: [{ + name: 'Roboto', + styles: 'wght@100;300;400;500;700;900', + }], + }, + }), + ], + define: { 'process.env': {} }, + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + }, + extensions: [ + '.js', + '.json', + '.jsx', + '.mjs', + '.ts', + '.tsx', + '.vue', + ], + }, + server: { + port: 3002, + }, + optimizeDeps: { + include: commonjsDeps, + }, + build: { + commonjsOptions: { + transformMixedEsModules: true, + }, + } +})