diff --git a/pkg/api/http/controller/groups/settings.py b/pkg/api/http/controller/groups/settings.py index 72f41d97..9d8727be 100644 --- a/pkg/api/http/controller/groups/settings.py +++ b/pkg/api/http/controller/groups/settings.py @@ -28,6 +28,9 @@ class SettingsRouterGroup(group.RouterGroup): manager = self.ap.settings_mgr.get_manager(manager_name) + if manager is None: + return self.fail(1, '配置管理器不存在') + return self.success( data={ "manager": { @@ -44,7 +47,15 @@ class SettingsRouterGroup(group.RouterGroup): async def _(manager_name: str) -> str: data = await quart.request.json manager = self.ap.settings_mgr.get_manager(manager_name) - manager.data = data['data'] + + 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/main.py b/pkg/api/http/controller/main.py index 17b15192..13b1afcf 100644 --- a/pkg/api/http/controller/main.py +++ b/pkg/api/http/controller/main.py @@ -30,11 +30,12 @@ class HTTPController: while True: await asyncio.sleep(1) - asyncio.create_task(self.quart_app.run_task( + task = asyncio.create_task(self.quart_app.run_task( host=self.ap.system_cfg.data['http-api']['host'], port=self.ap.system_cfg.data['http-api']['port'], shutdown_trigger=shutdown_trigger_placeholder )) + self.ap.asyncio_tasks.append(task) async def register_routes(self) -> None: diff --git a/pkg/audit/center/apigroup.py b/pkg/audit/center/apigroup.py index 26eac7ae..6ae8be5d 100644 --- a/pkg/audit/center/apigroup.py +++ b/pkg/audit/center/apigroup.py @@ -69,7 +69,11 @@ class APIGroup(metaclass=abc.ABCMeta): **kwargs ) -> asyncio.Task: """执行请求""" - asyncio.create_task(self._do(method, path, data, params, headers, **kwargs)) + task = asyncio.create_task(self._do(method, path, data, params, headers, **kwargs)) + + self.ap.asyncio_tasks.append(task) + + return task def gen_rid( self diff --git a/pkg/config/settings.py b/pkg/config/settings.py index 9d1a0cbc..5265c6e5 100644 --- a/pkg/config/settings.py +++ b/pkg/config/settings.py @@ -46,7 +46,7 @@ class SettingsManager: manager.schema = schema self.managers.append(manager) - def get_manager(self, name: str) -> config_manager.ConfigManager: + def get_manager(self, name: str) -> config_manager.ConfigManager | None: """获取配置管理器 Args: @@ -60,7 +60,7 @@ class SettingsManager: if m.name == name: return m - raise ValueError(f'配置管理器 {name} 不存在') + return None def get_manager_list(self) -> list[config_manager.ConfigManager]: """获取配置管理器列表 diff --git a/pkg/core/app.py b/pkg/core/app.py index 11a1eed3..c67cb082 100644 --- a/pkg/core/app.py +++ b/pkg/core/app.py @@ -125,9 +125,11 @@ class Application: import signal def signal_handler(sig, frame): - for task in tasks: + for task in self.asyncio_tasks: task.cancel() self.logger.info("程序退出.") + # 结束当前事件循环 + self.event_loop.stop() exit(0) signal.signal(signal.SIGINT, signal_handler) @@ -138,4 +140,3 @@ class Application: except Exception as e: self.logger.error(f"应用运行致命异常: {e}") self.logger.debug(f"Traceback: {traceback.format_exc()}") - 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/load_config.py b/pkg/core/stages/load_config.py index 006af00f..9ae4c20c 100644 --- a/pkg/core/stages/load_config.py +++ b/pkg/core/stages/load_config.py @@ -3,6 +3,7 @@ from __future__ import annotations from .. import stage, app from ..bootutils import config from ...config import settings as settings_mgr +from ...utils import schema @stage.stage_class("LoadConfigStage") @@ -26,31 +27,36 @@ class LoadConfigStage(stage.BootingStage): ap.settings_mgr.register_manager( name="command.json", description="命令配置", - manager=ap.command_cfg + manager=ap.command_cfg, + schema=schema.CONFIG_COMMAND_SCHEMA ) ap.settings_mgr.register_manager( name="pipeline.json", description="消息处理流水线配置", - manager=ap.pipeline_cfg + manager=ap.pipeline_cfg, + schema=schema.CONFIG_PIPELINE_SCHEMA ) ap.settings_mgr.register_manager( name="platform.json", description="消息平台配置", - manager=ap.platform_cfg + manager=ap.platform_cfg, + schema=schema.CONFIG_PLATFORM_SCHEMA ) ap.settings_mgr.register_manager( name="provider.json", description="大模型能力配置", - manager=ap.provider_cfg + manager=ap.provider_cfg, + schema=schema.CONFIG_PROVIDER_SCHEMA ) ap.settings_mgr.register_manager( name="system.json", description="系统配置", - manager=ap.system_cfg + manager=ap.system_cfg, + schema=schema.CONFIG_SYSTEM_SCHEMA ) ap.plugin_setting_meta = await config.load_json_config("plugins/plugins.json", "templates/plugin-settings.json") diff --git a/pkg/core/stages/migrate.py b/pkg/core/stages/migrate.py index a4e57e78..dd9023ed 100644 --- a/pkg/core/stages/migrate.py +++ b/pkg/core/stages/migrate.py @@ -6,7 +6,7 @@ from .. import stage, app 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, m013_http_api_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/pipeline/controller.py b/pkg/pipeline/controller.py index 0f07e068..3b4e2c64 100644 --- a/pkg/pipeline/controller.py +++ b/pkg/pipeline/controller.py @@ -60,7 +60,9 @@ class Controller: # 通知其他协程,有新的请求可以处理了 self.ap.query_pool.condition.notify_all() - asyncio.create_task(_process_query(selected_query)) + task = asyncio.create_task(_process_query(selected_query)) + self.ap.asyncio_tasks.append(task) + except Exception as e: # traceback.print_exc() self.ap.logger.error(f"控制器循环出错: {e}") diff --git a/pkg/pipeline/respback/respback.py b/pkg/pipeline/respback/respback.py index d3dd83fe..08b335d5 100644 --- a/pkg/pipeline/respback/respback.py +++ b/pkg/pipeline/respback/respback.py @@ -19,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/platform/manager.py b/pkg/platform/manager.py index aed8deff..4b22d5c9 100644 --- a/pkg/platform/manager.py +++ b/pkg/platform/manager.py @@ -184,7 +184,8 @@ class PlatformManager: tasks.append(exception_wrapper(adapter)) for task in tasks: - asyncio.create_task(task) + async_task = asyncio.create_task(task) + self.ap.asyncio_tasks.append(async_task) except Exception as e: self.ap.logger.error('平台适配器运行出错: ' + str(e)) 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/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/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-功能使用.md b/res/wiki/1-功能使用.md deleted file mode 100644 index e788f720..00000000 --- a/res/wiki/1-功能使用.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-功能常见问题.md b/res/wiki/2-功能常见问题.md deleted file mode 100644 index 88f881c8..00000000 --- a/res/wiki/2-功能常见问题.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-常见错误.md b/res/wiki/3-常见错误.md deleted file mode 100644 index 58f9519e..00000000 --- a/res/wiki/3-常见错误.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-技术信息.md b/res/wiki/4-技术信息.md deleted file mode 100644 index ac1ee76f..00000000 --- a/res/wiki/4-技术信息.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-插件使用.md b/res/wiki/5-插件使用.md deleted file mode 100644 index a8be95c1..00000000 --- a/res/wiki/5-插件使用.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-插件使用-内容函数.md b/res/wiki/6-插件使用-内容函数.md deleted file mode 100644 index b4055b29..00000000 --- a/res/wiki/6-插件使用-内容函数.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-插件开发.md b/res/wiki/7-插件开发.md deleted file mode 100644 index c9cd2657..00000000 --- a/res/wiki/7-插件开发.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-官方接口、ChatGPT网页版、ChatGPT-API区别.md b/res/wiki/8-官方接口、ChatGPT网页版、ChatGPT-API区别.md deleted file mode 100644 index 64bcc8d0..00000000 --- a/res/wiki/8-官方接口、ChatGPT网页版、ChatGPT-API区别.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配置.md b/res/wiki/9-go-cqhttp配置.md deleted file mode 100644 index f8aad243..00000000 --- a/res/wiki/9-go-cqhttp配置.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..f0a13fd6 100644 --- a/templates/platform.json +++ b/templates/platform.json @@ -37,7 +37,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..4c2bab31 --- /dev/null +++ b/templates/schema/platform.json @@ -0,0 +1,258 @@ +{ + "type": "object", + "layout": "expansion-panels", + "properties": { + "platform-adapters": { + "type": "array", + "title": "消息平台适配器", + "default": {}, + "items": { + "type": "object", + "oneOf": [ + { + "title": "YiriMirai 适配器", + "description": "用于接入 Mirai", + "properties": { + "adapter": { + "type": "string", + "const": "yiri-mirai" + }, + "enable": { + "type": "boolean", + "default": false, + "description": "是否启用此适配器", + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + } + }, + "host": { + "type": "string", + "default": "127.0.0.1" + }, + "port": { + "type": "integer", + "default": 8080 + }, + "verifyKey": { + "type": "string", + "default": "yirimirai" + }, + "qq": { + "type": "integer", + "default": 123456789 + } + } + }, + { + "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 时,QChatGPT 作为服务端被动等待框架连接,请在 Lagrange 等框架中设置被动 ws 地址或者反向 ws 地址(具体视框架而定)为 QChatGPT 监听的地址,且路径为/ws,例如:ws://127.0.0.1:8080/ws" + }, + "port": { + "type": "integer", + "default": 8080, + "description": "设置监听的端口,默认8080,需在 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..01286a3c --- /dev/null +++ b/templates/schema/system.json @@ -0,0 +1,121 @@ +{ + "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" + } + } + }, + "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/web/package-lock.json b/web/package-lock.json index 9067fd2b..b099d1a2 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,7 +8,13 @@ "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", "axios": "^1.7.7", "codemirror": "^5.65.18", "core-js": "^3.37.1", @@ -510,6 +516,30 @@ "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", @@ -564,6 +594,86 @@ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "license": "MIT" }, + "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/@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/@mdi/font": { "version": "7.4.47", "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.4.47.tgz", @@ -860,6 +970,34 @@ "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", @@ -1027,6 +1165,94 @@ "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", @@ -1051,22 +1277,62 @@ } }, "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, + "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.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "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-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1081,7 +1347,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -1111,7 +1376,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { @@ -1262,6 +1526,12 @@ "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", @@ -1299,7 +1569,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/binary-extensions": { @@ -1326,7 +1595,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -1416,7 +1684,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -1477,7 +1744,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -1490,7 +1756,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -1509,7 +1774,6 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/confbox": { @@ -1622,7 +1886,6 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "devOptional": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1701,6 +1964,21 @@ "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", @@ -2339,6 +2617,30 @@ "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/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -2413,7 +2715,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -2460,6 +2761,12 @@ "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", @@ -2483,6 +2790,36 @@ "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", @@ -2792,7 +3129,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2876,6 +3212,16 @@ "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", @@ -3261,6 +3607,24 @@ "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/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -3282,10 +3646,9 @@ "license": "MIT" }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, + "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-stable-stringify-without-jsonify": { @@ -3332,6 +3695,15 @@ "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", @@ -3401,6 +3773,51 @@ "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/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3450,7 +3867,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -3486,7 +3902,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "devOptional": true, "license": "MIT" }, "node_modules/nanoid": { @@ -3923,6 +4338,15 @@ "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", @@ -4304,7 +4728,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -4471,6 +4894,12 @@ "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", diff --git a/web/package.json b/web/package.json index 8f961de1..71ca5ba8 100644 --- a/web/package.json +++ b/web/package.json @@ -8,7 +8,13 @@ "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", "axios": "^1.7.7", "codemirror": "^5.65.18", "core-js": "^3.37.1", diff --git a/web/src/App.vue b/web/src/App.vue index 9b86a7e5..54763a00 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -1,5 +1,8 @@