diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 88b75dc35..452ae1092 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -200,6 +200,23 @@ export default defineConfig({ items: [ { text: "开发配置", link: "/zh/develop/dev-config" }, { text: "组件架构", link: "/zh/develop/comp-arch" }, + { + text: "适配器开发", + collapsed: true, + items: [ + { + text: "Discord 适配器", + collapsed: true, + items: [ + { text: "概述", link: "/zh/develop/adapter/discord/README" }, + { text: "快速开始", link: "/zh/develop/adapter/discord/quick_start" }, + { text: "架构设计", link: "/zh/develop/adapter/discord/design" }, + { text: "API 参考", link: "/zh/develop/adapter/discord/api_reference" }, + { text: "故障排除", link: "/zh/develop/adapter/discord/troubleshooting" }, + ], + }, + ], + }, ], }, ], diff --git a/docs/zh/develop/adapter/discord/README.md b/docs/zh/develop/adapter/discord/README.md new file mode 100644 index 000000000..a822d98ac --- /dev/null +++ b/docs/zh/develop/adapter/discord/README.md @@ -0,0 +1,176 @@ +# Discord 适配器文档 + +## 概述 + +这是 LangBot 平台的 Discord 适配器完整文档,基于 discord.py 库实现了与 Discord 平台的全面集成,包括文本消息、图像处理、语音功能等完整功能集。 + +## 文档导航 + +### 核心文档 +- **[API 参考](api_reference.md)** - Discord 适配器完整 API 文档 +- **[快速开始](quick_start.md)** - 基本使用指南 +- **[架构设计](design.md)** - 适配器架构设计 +- **[故障排除](troubleshooting.md)** - 常见问题解决方案 + +## 完整功能集 + +### 消息处理 +- 文本消息发送和接收 +- 图像消息处理(支持多种格式) +- 消息引用和回复 +- At 功能支持 +- 转发消息处理 + +### 图像功能 +- 多格式图像支持(PNG、JPG、GIF、WebP) +- Base64 编码图像处理 +- URL 图像下载和转换 +- 本地文件图像读取 +- 自动文件类型检测 +- 批量图像发送 + +### 语音功能 +- 语音频道连接管理 +- 音频播放控制 +- 连接状态监控 +- 权限验证系统 +- 自动连接清理 + +### 事件系统 +- 消息事件处理 +- 用户状态监控 +- 频道状态变化 +- Bot 生命周期管理 + +## 快速开始 + +### 环境要求 + +- **LangBot**: 框架环境 + +## 系统架构 + +### 核心组件 + +```mermaid +graph TD + A[Discord Adapter] --> B[DiscordAdapter] + A --> C[DiscordMessageConverter] + A --> D[DiscordEventConverter] + A --> E[VoiceConnectionManager] + A --> F[MyClient] + + B --> G[消息发送/接收] + C --> H[格式转换] + D --> I[事件处理] + E --> J[语音连接] + F --> K[Discord API] +``` + +### 消息处理流程 + +```mermaid +sequenceDiagram + participant U as Discord User + participant D as Discord API + participant A as DiscordAdapter + participant E as EventConverter + participant P as Plugin + participant M as MessageConverter + + U->>D: 发送消息 + D->>A: Discord Message + A->>E: target2yiri() + E->>P: MessageEvent + P->>A: 回复请求 + A->>M: yiri2target() + M->>D: Discord 格式 + D->>U: 显示回复 +``` + +### 图像处理流程 + +```mermaid +graph TD + A[Image Element] --> B{图像来源} + B -->|base64| C[Base64 解码] + B -->|url| D[HTTP 下载] + B -->|path| E[文件读取] + + C --> F[格式检测] + D --> F + E --> F + + F --> G[文件名生成] + G --> H[discord.File 创建] + H --> I[上传到 Discord] +``` + +### 语音处理流程 + +```mermaid +graph TD + A[语音指令] --> B[权限检查] + B --> C{用户在频道?} + C -->|否| D[权限错误] + C -->|是| E[Bot权限检查] + E --> F{有连接权限?} + F -->|否| D + F -->|是| G[建立连接] + G --> H[音频处理] + H --> I[Discord Voice API] +``` + +## 核心特性详解 + +### 消息转换器 (DiscordMessageConverter) + +负责在 LangBot 内部消息格式和 Discord API 格式之间进行转换: + +- **yiri2target**: 将 LangBot MessageChain 转换为 Discord 格式 +- **target2yiri**: 将 Discord 消息转换为 LangBot 格式 +- 支持文本、图像、转发消息等多种类型 + +### 图像处理特性 + +- **多源支持**: Base64、URL、本地文件路径 +- **格式检测**: 自动检测 PNG、JPG、GIF、WebP 格式 +- **智能命名**: 基于内容和来源的文件命名 +- **错误处理**: 优雅处理无效图像和网络错误 +- **批量处理**: 单次消息支持多张图像 + +### 语音连接管理器 (VoiceConnectionManager) + +提供完整的语音功能管理: + +- **连接池管理**: 多服务器连接状态跟踪 +- **权限验证**: 用户和机器人权限检查 +- **状态监控**: 连接健康度和延迟监控 +- **异常处理**: 分类的异常体系 +- **自动清理**: 定期清理无效连接 + +### 事件系统 + +- **消息事件**: 处理用户消息和机器人响应 +- **状态监控**: 跟踪连接状态变化 +- **生命周期**: 管理适配器启动和关闭 + +## 权限配置 + +Bot 需要以下 Discord 权限: + +- **基础权限**: + - Read Messages + - Send Messages + - Embed Links + - Attach Files + - Read Message History + +- **语音权限**: + - Connect + - Speak + - Use Voice Activity + +--- + +> **注意**: 本文档基于实际的 discord.py 实现,涵盖了 Discord 适配器的完整功能集。语音功能需要额外的依赖安装,图像功能支持多种来源和格式。详细的使用方法和故障排除请参考其他专门文档。 diff --git a/docs/zh/develop/adapter/discord/api_reference.md b/docs/zh/develop/adapter/discord/api_reference.md new file mode 100644 index 000000000..b730f2dd5 --- /dev/null +++ b/docs/zh/develop/adapter/discord/api_reference.md @@ -0,0 +1,551 @@ +# Discord 适配器 API 参考 + +## 概述 + +Discord 适配器提供完整的 Discord 平台集成功能,包括消息处理、图像管理、语音连接等。所有功能基于 discord.py 库的原生 API 实现。 + +## 核心 API + +### DiscordAdapter 类 + +Discord 平台适配器主类,提供与 Discord 平台交互的完整接口。 + +#### 消息相关方法 + +##### `send_message(target_type, target_id, message)` + +发送消息到指定频道。 + +**参数:** +- `target_type` (str): 目标类型,通常为 "channel" +- `target_id` (str): 目标频道 ID +- `message` (MessageChain): 要发送的消息链 + +**功能:** +- 支持文本消息发送 +- 支持图像文件批量发送 +- 自动处理消息格式转换 + +**示例:** +```python +# 发送纯文本消息 +await adapter.send_message("channel", "123456789", MessageChain([Plain("Hello!")])) + +# 发送带图像的消息 +message_chain = MessageChain([ + Plain("看看这张图片:"), + Image(path="path/to/image.jpg"), + Image(url="https://example.com/image.png") +]) +await adapter.send_message("channel", "123456789", message_chain) +``` + +--- + +##### `reply_message(message_source, message, quote_origin=False)` + +回复指定消息。 + +**参数:** +- `message_source` (MessageEvent): 要回复的原始消息事件 +- `message` (MessageChain): 回复内容 +- `quote_origin` (bool): 是否引用原消息 + +**示例:** +```python +# 简单回复 +await adapter.reply_message( + message_event, + MessageChain([Plain("收到你的消息了!")]) +) + +# 引用回复 +await adapter.reply_message( + message_event, + MessageChain([Plain("这是对你消息的回复")]), + quote_origin=True +) +``` + +#### 语音相关方法 + +##### `join_voice_channel(guild_id, channel_id, user_id=None)` + +加入指定的语音频道。 + +**参数:** +- `guild_id` (int): Discord 服务器 ID +- `channel_id` (int): 语音频道 ID +- `user_id` (int, optional): 请求用户 ID,用于权限验证 + +**返回值:** +- `discord.VoiceClient`: 语音客户端对象 + +**异常:** +- `VoicePermissionError`: 权限不足 +- `VoiceNetworkError`: 网络连接问题 +- `VoiceConnectionError`: 其他连接错误 + +**示例:** +```python +try: + voice_client = await adapter.join_voice_channel( + guild_id=123456789, + channel_id=987654321, + user_id=555666777 + ) + print(f"成功连接,延迟: {voice_client.latency*1000:.2f}ms") +except VoicePermissionError as e: + print(f"权限错误: {e.missing_permissions}") +``` + +--- + +##### `leave_voice_channel(guild_id)` + +离开指定服务器的语音频道。 + +**参数:** +- `guild_id` (int): Discord 服务器 ID + +**返回值:** +- `bool`: 成功断开返回 `True` + +**示例:** +```python +success = await adapter.leave_voice_channel(guild_id=123456789) +if success: + print("成功离开语音频道") +``` + +--- + +##### `get_voice_client(guild_id)` + +获取指定服务器的语音客户端对象。 + +**参数:** +- `guild_id` (int): Discord 服务器 ID + +**返回值:** +- `discord.VoiceClient`: 语音客户端对象,如果存在连接 +- `None`: 如果没有活跃连接 + +**示例:** +```python +voice_client = await adapter.get_voice_client(guild_id=123456789) +if voice_client: + # 播放音频 + import discord + audio = discord.FFmpegPCMAudio("path/to/audio.mp3") + voice_client.play(audio) +``` + +--- + +##### `is_connected_to_voice(guild_id)` + +检查是否连接到指定服务器的语音频道。 + +**参数:** +- `guild_id` (int): Discord 服务器 ID + +**返回值:** +- `bool`: 已连接返回 `True` + +**示例:** +```python +if await adapter.is_connected_to_voice(guild_id=123456789): + print("已连接到语音频道") +``` + +--- + +##### `get_voice_connection_status(guild_id)` + +获取语音连接的详细状态信息。 + +**参数:** +- `guild_id` (int): Discord 服务器 ID + +**返回值:** +- `dict`: 连接状态信息字典,如果存在连接 +- `None`: 如果没有连接 + +**状态信息结构:** +```python +{ + 'guild_id': int, + 'channel_id': int, + 'channel_name': str, + 'connected': bool, + 'connection_time': datetime, + 'last_activity': datetime, + 'status': str, # VoiceConnectionStatus 枚举值 + 'user_count': int, + 'latency': float, + 'connection_health': str +} +``` + +--- + +##### `list_active_voice_connections()` + +获取所有活跃的语音连接列表。 + +**返回值:** +- `List[dict]`: 活跃连接状态列表 + +**示例:** +```python +connections = await adapter.list_active_voice_connections() +for conn in connections: + print(f"服务器: {conn['guild_id']}, 频道: {conn['channel_name']}") +``` + +--- + +##### `get_voice_channel_info(guild_id, channel_id)` + +获取指定语音频道的详细信息。 + +**参数:** +- `guild_id` (int): 服务器 ID +- `channel_id` (int): 频道 ID + +**返回值:** +- `dict`: 频道信息字典 +- `None`: 如果频道不存在 + +**频道信息结构:** +```python +{ + 'channel_id': int, + 'channel_name': str, + 'guild_id': int, + 'guild_name': str, + 'user_limit': int, + 'current_users': List[dict], + 'user_count': int, + 'bitrate': int, + 'permissions': dict +} +``` + +#### 管理方法 + +##### `is_muted(group_id)` + +检查群组是否被静音(Discord 适配器始终返回 False)。 + +**参数:** +- `group_id` (int): 群组 ID + +**返回值:** +- `bool`: 是否静音 + +--- + +##### `register_listener(event_type, callback)` + +注册事件监听器。 + +**参数:** +- `event_type` (Type[Event]): 事件类型 +- `callback` (Callable): 回调函数 + +--- + +##### `unregister_listener(event_type, callback)` + +注销事件监听器。 + +**参数:** +- `event_type` (Type[Event]): 事件类型 +- `callback` (Callable): 回调函数 + +--- + +##### `run_async()` + +启动 Discord 适配器,建立与 Discord 的连接。 + +**功能:** +- 初始化语音管理器 +- 启动连接监控 +- 建立 WebSocket 连接 + +--- + +##### `kill()` + +关闭 Discord 适配器。 + +**返回值:** +- `bool`: 是否成功关闭 + +**功能:** +- 断开所有语音连接 +- 关闭 Discord 客户端 +- 清理资源 + +## 消息转换器 API + +### DiscordMessageConverter 类 + +负责在 LangBot 和 Discord 消息格式之间进行转换。 + +#### `yiri2target(message_chain)` + +将 LangBot MessageChain 转换为 Discord 格式。 + +**参数:** +- `message_chain` (MessageChain): LangBot 消息链 + +**返回值:** +- `Tuple[str, List[discord.File]]`: (文本内容, 文件列表) + +**支持的消息元素:** +- `Plain`: 纯文本内容 +- `Image`: 图像内容(支持 base64、URL、文件路径) +- `At`: @ 提及(会被移除) +- `Forward`: 转发消息 + +**图像处理特性:** +- **Base64 图像**: 自动检测格式并解码 +- **URL 图像**: 异步下载并转换 +- **文件路径**: 读取本地文件 +- **格式检测**: 支持 PNG、JPG、GIF、WebP +- **错误处理**: 跳过无效图像 + +**示例:** +```python +message_chain = MessageChain([ + Plain("文本内容"), + Image(base64="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="), + Image(url="https://example.com/image.jpg"), + Image(path="/path/to/local/image.png") +]) + +text, files = await DiscordMessageConverter.yiri2target(message_chain) +# text: "文本内容" +# files: [discord.File, discord.File, discord.File] +``` + +--- + +#### `target2yiri(message)` + +将 Discord 消息转换为 LangBot MessageChain。 + +**参数:** +- `message` (discord.Message): Discord 消息对象 + +**返回值:** +- `MessageChain`: LangBot 消息链 + +**转换规则:** +- 文本内容 → `Plain` 元素 +- 附件图像 → `Image` 元素 +- 嵌入内容 → `Plain` 元素(描述文本) + +## 事件转换器 API + +### DiscordEventConverter 类 + +负责将 Discord 事件转换为 LangBot 平台事件。 + +#### `target2yiri(message)` + +将 Discord 消息转换为 LangBot MessageEvent。 + +**参数:** +- `message` (discord.Message): Discord 消息对象 + +**返回值:** +- `MessageEvent`: LangBot 消息事件 + +**事件字段:** +- `message_chain`: 转换后的消息链 +- `sender`: 发送者信息 +- `query`: 查询上下文信息 + +## 异常类 + +### VoiceConnectionError + +基础语音连接异常类。 + +**属性:** +- `error_code` (str): 错误代码 +- `guild_id` (int): 相关服务器 ID +- `timestamp` (datetime): 错误发生时间 + +**常见错误代码:** +- `"GUILD_NOT_FOUND"`: 服务器不存在 +- `"CHANNEL_NOT_FOUND"`: 频道不存在 +- `"MANAGER_NOT_READY"`: 语音管理器未初始化 +- `"OPUS_NOT_LOADED"`: Opus编码器未加载 +- `"UNKNOWN_ERROR"`: 未知错误 + +### VoicePermissionError + +语音权限异常,继承自 `VoiceConnectionError`。 + +**属性:** +- `missing_permissions` (List[str]): 缺失的权限列表 +- `user_id` (int): 相关用户 ID +- `channel_id` (int): 相关频道 ID + +**常见权限错误:** +- `"user_not_in_channel"`: 用户不在语音频道中 +- `"member_not_found"`: 找不到用户 +- `"connect"`: Bot缺少连接权限 +- `"speak"`: Bot缺少发言权限 + +### VoiceNetworkError + +语音网络异常,继承自 `VoiceConnectionError`。 + +**属性:** +- `retry_count` (int): 重试次数 +- `last_attempt` (datetime): 最后尝试时间 + +## 使用示例 + +### 综合消息处理 + +```python +async def handle_complex_message(adapter, channel_id): + """处理包含多种元素的复杂消息""" + + # 构建包含文本和图像的消息 + message_chain = MessageChain([ + Plain("这是一个包含多种内容的消息:\\n"), + Plain("1. 文本内容\\n"), + Plain("2. 网络图像\\n"), + Image(url="https://example.com/image1.jpg"), + Plain("3. 本地图像\\n"), + Image(path="/path/to/local/image.png"), + Plain("4. Base64 图像\\n"), + Image(base64="data:image/png;base64,iVBORw0KGgo...") + ]) + + # 发送消息 + await adapter.send_message("channel", channel_id, message_chain) +``` + +### 语音播放控制 + +```python +async def voice_player_example(adapter, guild_id, channel_id, audio_files): + """语音播放器示例""" + + # 连接到语音频道 + try: + voice_client = await adapter.join_voice_channel(guild_id, channel_id) + except VoicePermissionError as e: + print(f"权限不足: {e.missing_permissions}") + return + + # 播放音频队列 + for audio_file in audio_files: + if voice_client.is_connected(): + # 等待当前播放完成 + while voice_client.is_playing(): + await asyncio.sleep(1) + + # 播放下一个音频 + audio_source = discord.FFmpegPCMAudio(audio_file) + voice_client.play(audio_source) + print(f"正在播放: {audio_file}") + + # 播放完成后离开频道 + await adapter.leave_voice_channel(guild_id) +``` + +### 状态监控 + +```python +async def monitor_connections(adapter): + """监控所有语音连接状态""" + + connections = await adapter.list_active_voice_connections() + + for conn in connections: + print(f"服务器: {conn['guild_id']}") + print(f"频道: {conn['channel_name']}") + print(f"延迟: {conn['latency']:.2f}ms") + print(f"用户数: {conn['user_count']}") + print(f"健康度: {conn['connection_health']}") + print("---") +``` + +## 最佳实践 + +### 错误处理 + +```python +async def safe_discord_operation(): + try: + # Discord 操作 + pass + except VoicePermissionError as e: + if "user_not_in_channel" in e.missing_permissions: + await send_error("请先加入语音频道") + elif "connect" in e.missing_permissions: + await send_error("机器人没有连接权限") + except discord.HTTPException as e: + if e.status == 403: + await send_error("权限不足") + elif e.status == 404: + await send_error("频道不存在") + except Exception as e: + await send_error(f"未知错误: {e}") +``` + +### 资源管理 + +```python +async def with_voice_connection(adapter, guild_id, channel_id, operation): + """安全的语音连接管理""" + voice_client = None + try: + voice_client = await adapter.join_voice_channel(guild_id, channel_id) + await operation(voice_client) + finally: + if voice_client: + await adapter.leave_voice_channel(guild_id) +``` + +### 图像处理优化 + +```python +async def batch_image_processing(image_list): + """批量图像处理""" + processed_images = [] + + for image_element in image_list: + try: + if image_element.url: + # 异步下载 + async with aiohttp.ClientSession() as session: + async with session.get(image_element.url) as response: + image_data = await response.read() + processed_images.append(process_image_data(image_data)) + elif image_element.path: + # 本地文件处理 + with open(image_element.path, 'rb') as f: + image_data = f.read() + processed_images.append(process_image_data(image_data)) + except Exception as e: + print(f"图像处理失败: {e}") + continue + + return processed_images +``` + +--- + +> **注意**: 本 API 文档基于实际的 discord.py 实现,涵盖了 Discord 适配器的完整功能集。图像处理功能无需额外依赖,语音功能需要安装可选依赖。 diff --git a/docs/zh/develop/adapter/discord/design.md b/docs/zh/develop/adapter/discord/design.md new file mode 100644 index 000000000..880ca3efa --- /dev/null +++ b/docs/zh/develop/adapter/discord/design.md @@ -0,0 +1,397 @@ +# Discord 适配器架构设计 + +## 概述 + +本文档详细描述 LangBot Discord 适配器的架构设计,包括消息处理、图像管理、语音功能等完整系统的设计理念和实现架构。 + +## 整体架构 + +### 系统组件图 + +```mermaid +graph TB + subgraph "LangBot 框架层" + A[EventContext] --> B[DiscordAdapter] + A --> C[MessageEvent] + A --> D[Plugin System] + end + + subgraph "Discord 适配器层" + B --> E[DiscordMessageConverter] + B --> F[DiscordEventConverter] + B --> G[VoiceConnectionManager] + B --> H[MyClient] + end + + subgraph "Discord.py 库层" + H --> I[discord.Client] + G --> J[discord.VoiceClient] + E --> K[discord.File] + I --> L[Discord WebSocket] + J --> L + end + + subgraph "外部服务" + L --> M[Discord API] + N[HTTP Requests] --> O[Image URLs] + P[Local Files] --> E + Q[Audio Files] --> J + end + + subgraph "依赖库" + R[aiohttp] --> N + S[PyNaCl] --> J + T[FFmpeg] --> J + end +``` + +### 数据流架构 + +```mermaid +sequenceDiagram + participant U as User + participant D as Discord + participant A as DiscordAdapter + participant E as EventConverter + participant M as MessageConverter + participant P as Plugin + + U->>D: 发送消息 + D->>A: Discord Message + A->>E: target2yiri() + E->>A: MessageEvent + A->>P: 事件分发 + P->>A: 回复消息 + A->>M: yiri2target() + M->>A: Discord格式 + A->>D: 发送回复 + D->>U: 显示回复 +``` + +## 核心组件设计 + +### 1. DiscordAdapter 主适配器 + +#### 职责 +- **连接管理**: 维护与 Discord 的 WebSocket 连接 +- **事件路由**: 将 Discord 事件转换并分发到插件系统 +- **消息发送**: 处理外发消息的格式转换和发送 +- **语音管理**: 协调语音连接的生命周期 +- **资源清理**: 管理连接池和内存资源 + +#### 核心接口 +```python +class DiscordAdapter(MessagePlatformAdapter): + # 消息相关 + async def send_message(target_type: str, target_id: str, message: MessageChain) + async def reply_message(message_source: MessageEvent, message: MessageChain, quote_origin: bool) + + # 语音相关 + async def join_voice_channel(guild_id: int, channel_id: int, user_id: int) -> VoiceClient + async def leave_voice_channel(guild_id: int) -> bool + async def get_voice_client(guild_id: int) -> Optional[VoiceClient] + + # 管理相关 + async def run_async() + async def kill() -> bool +``` + +#### 设计模式 +- **适配器模式**: 将 Discord API 适配到 LangBot 接口 +- **单例模式**: 每个 Discord 配置对应一个适配器实例 +- **观察者模式**: 事件监听和分发机制 + +### 2. 消息转换器 (DiscordMessageConverter) + +#### 设计目标 +- **格式无关**: 屏蔽 Discord 和 LangBot 格式差异 +- **类型安全**: 保证消息元素类型转换的正确性 +- **性能优化**: 减少不必要的数据复制和转换 +- **错误恢复**: 优雅处理格式错误和网络异常 + +#### 转换策略 + +##### LangBot → Discord (yiri2target) +```python +转换流程: +MessageChain → 解析元素 → 分类处理 → (text: str, files: List[discord.File]) + +元素处理策略: +- Plain → 文本累加 +- Image → 文件对象创建 +- At → 移除处理 +- Forward → 递归展开 +``` + +##### Discord → LangBot (target2yiri) +```python +转换流程: +discord.Message → 提取内容 → 构建元素 → MessageChain + +内容映射: +- message.content → Plain +- message.attachments → Image +- message.embeds → Plain (description) +``` + +#### 图像处理架构 + +```mermaid +graph TD + A[Image Element] --> B{来源类型} + B -->|base64| C[Base64 解码] + B -->|url| D[HTTP 下载] + B -->|path| E[文件读取] + + C --> F[格式检测] + D --> F + E --> F + + F --> G[文件名生成] + G --> H[discord.File 创建] + + I[错误处理] --> J[跳过无效图像] + F --> I + D --> I + E --> I +``` + +#### 图像处理特性 +- **多源支持**: Base64、URL、本地文件 +- **格式智能检测**: 基于内容和元数据 +- **异步下载**: 并发处理多个网络图像 +- **内存优化**: 使用 BytesIO 避免临时文件 +- **错误隔离**: 单个图像失败不影响其他处理 + +### 3. 语音连接管理器 (VoiceConnectionManager) + +#### 设计原则 +- **连接复用**: 避免重复连接开销 +- **状态一致性**: 维护准确的连接状态 +- **权限验证**: 多层次权限检查 +- **故障恢复**: 自动重连和清理机制 +- **性能监控**: 连接质量实时监控 + +#### 连接生命周期 + +```mermaid +stateDiagram-v2 + [*] --> IDLE + IDLE --> CONNECTING: join_voice_channel() + CONNECTING --> CONNECTED: 连接成功 + CONNECTING --> FAILED: 连接失败 + CONNECTED --> PLAYING: 播放音频 + PLAYING --> CONNECTED: 播放完成/停止 + CONNECTED --> RECONNECTING: 网络中断 + RECONNECTING --> CONNECTED: 重连成功 + RECONNECTING --> FAILED: 重连失败 + FAILED --> IDLE: 清理资源 + CONNECTED --> IDLE: leave_voice_channel() + PLAYING --> IDLE: leave_voice_channel() +``` + +#### 连接池设计 +```python +class VoiceConnectionManager: + connections: Dict[int, VoiceConnectionInfo] # guild_id -> 连接信息 + + # 连接管理 + async def join_voice_channel() -> VoiceClient + async def leave_voice_channel() -> bool + async def get_connection_status() -> Optional[dict] + + # 监控和清理 + async def start_monitoring() + async def cleanup_inactive_connections() + async def disconnect_all() +``` + +#### 权限验证架构 +```mermaid +graph TD + A[连接请求] --> B[用户权限检查] + B --> C{用户在频道?} + C -->|否| D[VoicePermissionError] + C -->|是| E[Bot权限检查] + E --> F{Connect权限?} + F -->|否| G[VoicePermissionError] + F -->|是| H{Speak权限?} + H -->|否| I[VoicePermissionError] + H -->|是| J[建立连接] +``` + +### 4. 事件转换器 (DiscordEventConverter) + +#### 转换策略 +- **事件映射**: Discord 事件到 LangBot 事件的标准化 +- **上下文构建**: 构建完整的事件上下文信息 +- **用户信息**: 提取和标准化用户身份信息 +- **频道信息**: 处理频道和服务器相关信息 + +#### 事件转换流程 +```python +discord.Message +└── 提取基础信息 (作者、频道、时间) +└── 转换消息内容 (DiscordMessageConverter) +└── 构建查询对象 (Query) +└── 创建发送者对象 (Sender) +└── 生成 MessageEvent +``` + +## 异常处理设计 + +### 异常体系架构 + +```mermaid +classDiagram + Exception <|-- VoiceConnectionError + VoiceConnectionError <|-- VoicePermissionError + VoiceConnectionError <|-- VoiceNetworkError + + class VoiceConnectionError { + +error_code: str + +guild_id: int + +timestamp: datetime + } + + class VoicePermissionError { + +missing_permissions: List[str] + +user_id: int + +channel_id: int + } + + class VoiceNetworkError { + +retry_count: int + +last_attempt: datetime + } +``` + +### 错误处理策略 +- **分层处理**: 不同层次处理不同类型的错误 +- **优雅降级**: 单个功能失败不影响整体系统 +- **用户友好**: 提供清晰的错误信息和解决建议 +- **日志记录**: 完整的错误追踪和调试信息 + +## 性能设计 + +### 内存管理 +- **连接池**: 复用语音连接减少开销 +- **图像缓存**: 临时图像数据及时清理 +- **异步处理**: 避免阻塞主事件循环 +- **资源限制**: 控制并发连接和文件大小 + +### 并发设计 +- **异步架构**: 全面采用 async/await +- **连接锁**: 防止语音连接竞态条件 +- **批量处理**: 图像和消息的批量操作 +- **错误隔离**: 单个操作失败不影响其他操作 + +### 缓存策略 +```python +# 连接状态缓存 +connections: Dict[int, VoiceConnectionInfo] + +# 临时数据缓存 +image_cache: Dict[str, bytes] # URL -> 图像数据 +channel_cache: Dict[int, discord.Channel] # 频道对象缓存 +``` + +## 扩展性设计 + +### 插件接口 +- **标准化事件**: 提供统一的事件接口 +- **类型安全**: 强类型的消息元素系统 +- **回调机制**: 灵活的事件监听和处理 +- **生命周期**: 完整的插件生命周期管理 + +### 配置管理 +```python +# 适配器配置 +class DiscordConfig: + client_id: str + token: str + proxy: Optional[str] + voice_enabled: bool = True + image_max_size: int = 25 * 1024 * 1024 # 25MB + max_concurrent_downloads: int = 5 +``` + +### 监控接口 +- **连接状态**: 实时语音连接状态 +- **性能指标**: 延迟、吞吐量等指标 +- **错误统计**: 各类错误的统计和趋势 +- **资源使用**: 内存、连接数等资源监控 + +## 测试设计 + +### 单元测试架构 +```python +# 消息转换测试 +class TestDiscordMessageConverter: + async def test_yiri2target_text() + async def test_yiri2target_images() + async def test_target2yiri() + +# 语音功能测试 +class TestVoiceConnectionManager: + async def test_join_voice_channel() + async def test_permission_validation() + async def test_connection_cleanup() + +# 适配器集成测试 +class TestDiscordAdapter: + async def test_message_sending() + async def test_event_handling() + async def test_voice_integration() +``` + +### 模拟测试 +- **Mock Discord API**: 模拟 Discord 服务器响应 +- **虚拟语音频道**: 测试语音功能无需真实连接 +- **错误注入**: 测试各种异常情况的处理 +- **性能测试**: 并发和高负载测试 + +## 部署架构 + +### 环境要求 +- 与LangBot相同 + +## 监控和维护 + +### 日志设计 +```python +# 分级日志 +logger.debug("详细调试信息") +logger.info("一般操作信息") +logger.warning("警告信息") +logger.error("错误信息") +logger.critical("严重错误") + +# 结构化日志 +{ + "timestamp": "2025-07-04T10:30:00Z", + "level": "INFO", + "component": "VoiceConnectionManager", + "action": "join_voice_channel", + "guild_id": 123456789, + "channel_id": 987654321, + "latency": 45.2, + "status": "success" +} +``` + +### 健康检查 +- **连接状态**: Discord WebSocket 连接健康度 +- **语音质量**: 语音连接延迟和稳定性 +- **资源使用**: 内存和连接池状态 +- **错误率**: 各类操作的成功率统计 + +### 故障恢复 +- **自动重连**: WebSocket 连接断开后自动重连 +- **连接清理**: 定期清理无效的语音连接 +- **错误重试**: 网络错误的智能重试机制 +- **降级服务**: 语音功能异常时的降级策略 + +--- + +> **设计原则**: 本架构设计遵循模块化、可扩展、高性能的原则,确保 Discord 适配器能够稳定、高效地处理各种类型的消息和连接需求。 diff --git a/docs/zh/develop/adapter/discord/quick_start.md b/docs/zh/develop/adapter/discord/quick_start.md new file mode 100644 index 000000000..8149574a7 --- /dev/null +++ b/docs/zh/develop/adapter/discord/quick_start.md @@ -0,0 +1,551 @@ +# Discord 适配器快速入门 + +## 概述 + +本指南将引导您快速上手 LangBot Discord 适配器的完整功能,包括消息处理、图像功能和语音连接等。 + +## 前置条件 + +### 环境要求 +- Python 3.8+ +- LangBot 框架环境 +- discord.py、aiohttp(自动安装) +- discord.py[voice]、PyNaCl、FFmpeg(语音功能可选) + +### Discord 设置 + +本指南假设您已经创建好了 Discord 应用和 Bot。如需创建,请访问 [Discord Developer Portal](https://discord.com/developers/applications)。 + + +## 基本使用 + +### 1. 文本消息处理 + +```python +from pkg.platform.types import message as platform_message +from pkg.platform.types import events as platform_events + +@handler(platform_events.MessageEvent) +async def text_message_handler(ctx: EventContext): + """处理文本消息的基本示例""" + + # 获取适配器实例 + adapter = ctx.event.query.adapter + + # 提取消息文本 + text_content = "" + for element in ctx.event.message_chain: + if isinstance(element, platform_message.Plain): + text_content += element.text + + # 发送回复 + reply_chain = platform_message.MessageChain([ + platform_message.Plain(f"您说了: {text_content}") + ]) + + await adapter.reply_message(ctx.event, reply_chain, quote_origin=True) +``` + +### 2. 图像消息处理 + +```python +@handler(platform_events.MessageEvent) +async def image_message_handler(ctx: EventContext): + """处理图像消息的示例""" + + adapter = ctx.event.query.adapter + + # 检测消息中的图像 + image_count = 0 + for element in ctx.event.message_chain: + if isinstance(element, platform_message.Image): + image_count += 1 + + if image_count > 0: + # 回复图像信息 + reply_chain = platform_message.MessageChain([ + platform_message.Plain(f"收到 {image_count} 张图片!"), + platform_message.Plain("\\n正在处理...") + ]) + await adapter.reply_message(ctx.event, reply_chain) + + # 可以添加图像处理逻辑 + # processed_image = process_images(images) + + # 发送处理结果 + result_chain = platform_message.MessageChain([ + platform_message.Plain("处理完成!"), + # platform_message.Image(path="path/to/processed/image.jpg") + ]) + await adapter.send_message("channel", ctx.event.query.channel_id, result_chain) +``` + +### 3. 发送不同类型的图像 + +```python +async def send_various_images(adapter, channel_id): + """发送不同来源的图像示例""" + + message_chain = platform_message.MessageChain([ + platform_message.Plain("以下是不同类型的图像:\\n"), + + # 本地文件图像 + platform_message.Plain("1. 本地文件:\\n"), + platform_message.Image(path="/path/to/local/image.jpg"), + + # 网络图像 + platform_message.Plain("\\n2. 网络图像:\\n"), + platform_message.Image(url="https://example.com/image.png"), + + # Base64 图像 + platform_message.Plain("\\n3. Base64 图像:\\n"), + platform_message.Image(base64="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==") + ]) + + await adapter.send_message("channel", channel_id, message_chain) +``` + +## 语音功能使用 + +### 语音连接管理 + +```python +@handler(platform_events.MessageEvent) +async def voice_connect_handler(ctx: EventContext): + """语音连接示例""" + + adapter = ctx.event.query.adapter + + # 解析指令 + text = "" + for element in ctx.event.message_chain: + if isinstance(element, platform_message.Plain): + text += element.text + + if "加入语音" in text: + try: + # 这里需要根据实际情况获取 guild_id 和 channel_id + guild_id = get_guild_id_from_context(ctx) + channel_id = get_voice_channel_id_from_user(ctx.event.sender.id, guild_id) + + voice_client = await adapter.join_voice_channel( + guild_id=guild_id, + channel_id=channel_id, + user_id=ctx.event.sender.id + ) + + await ctx.reply("成功加入语音频道!") + + except VoicePermissionError as e: + if "user_not_in_channel" in e.missing_permissions: + await ctx.reply("请先加入语音频道") + elif "connect" in e.missing_permissions: + await ctx.reply("机器人没有连接权限") + else: + await ctx.reply(f"权限错误: {', '.join(e.missing_permissions)}") + + except Exception as e: + await ctx.reply(f"连接失败: {e}") + + elif "离开语音" in text: + guild_id = get_guild_id_from_context(ctx) + success = await adapter.leave_voice_channel(guild_id) + + if success: + await ctx.reply("已离开语音频道") + else: + await ctx.reply("未连接到语音频道") +``` + +### 音频播放 + +```python +@handler(platform_events.MessageEvent) +async def audio_play_handler(ctx: EventContext): + """音频播放示例""" + + adapter = ctx.event.query.adapter + + # 检查是否有播放音频的指令 + text = extract_text_from_message(ctx.event.message_chain) + + if "播放音频" in text: + guild_id = get_guild_id_from_context(ctx) + + # 获取语音客户端 + voice_client = await adapter.get_voice_client(guild_id) + + if not voice_client: + await ctx.reply("请先连接到语音频道") + return + + if voice_client.is_playing(): + await ctx.reply("正在播放其他音频,请稍候") + return + + try: + # 播放音频文件 + import discord + + # 配置 FFmpeg 选项 + ffmpeg_options = { + 'before_options': '-loglevel quiet', + 'options': '-vn -filter:a "volume=0.5"' + } + + # 创建音频源 + audio_source = discord.FFmpegPCMAudio( + "path/to/your/audio.mp3", + **ffmpeg_options + ) + + # 开始播放 + voice_client.play(audio_source) + await ctx.reply("开始播放音频") + + except Exception as e: + await ctx.reply(f"播放失败: {e}") + + elif "停止播放" in text: + guild_id = get_guild_id_from_context(ctx) + voice_client = await adapter.get_voice_client(guild_id) + + if voice_client and voice_client.is_playing(): + voice_client.stop() + await ctx.reply("已停止播放") + else: + await ctx.reply("当前没有播放音频") +``` + +### 语音状态查询 + +```python +@handler(platform_events.MessageEvent) +async def voice_status_handler(ctx: EventContext): + """语音状态查询示例""" + + adapter = ctx.event.query.adapter + text = extract_text_from_message(ctx.event.message_chain) + + if "语音状态" in text: + guild_id = get_guild_id_from_context(ctx) + + # 获取详细状态 + status = await adapter.get_voice_connection_status(guild_id) + + if status: + status_text = f"""语音连接状态: + 频道: {status['channel_name']} + 连接时间: {status['connection_time'].strftime('%H:%M:%S')} + 延迟: {status['latency']:.2f}ms + 用户数: {status['user_count']} + 健康度: {status['connection_health']} + 状态: {status['status']}""" + + await ctx.reply(status_text) + else: + await ctx.reply("当前未连接到语音频道") + + elif "连接列表" in text: + # 获取所有活跃连接 + connections = await adapter.list_active_voice_connections() + + if connections: + conn_list = "活跃语音连接:\\n" + for i, conn in enumerate(connections, 1): + conn_list += f"{i}. {conn['channel_name']} ({conn['user_count']} 用户)\\n" + await ctx.reply(conn_list) + else: + await ctx.reply("当前没有活跃的语音连接") +``` + +## 完整插件示例 + +以下是一个完整的 Discord 插件示例,包含所有主要功能: + +```python +import asyncio +import os +from pkg.platform.types import message as platform_message +from pkg.platform.types import events as platform_events +from pkg.core.entities.classes import EventContext +from pkg.plugin import handler + +# 辅助函数 +def extract_text_from_message(message_chain): + """从消息链中提取文本""" + text = "" + for element in message_chain: + if isinstance(element, platform_message.Plain): + text += element.text + return text.strip() + +def extract_images_from_message(message_chain): + """从消息链中提取图像""" + images = [] + for element in message_chain: + if isinstance(element, platform_message.Image): + images.append(element) + return images + +# 消息处理器 +@handler(platform_events.MessageEvent) +async def discord_message_handler(ctx: EventContext): + """Discord 消息处理主函数""" + + adapter = ctx.event.query.adapter + text = extract_text_from_message(ctx.event.message_chain) + images = extract_images_from_message(ctx.event.message_chain) + + # 文本处理 + if text.startswith("!echo"): + echo_text = text[5:].strip() + await ctx.reply(f"回声: {echo_text}") + + # 图像处理 + elif text.startswith("!image"): + if images: + reply_chain = platform_message.MessageChain([ + platform_message.Plain(f"检测到 {len(images)} 张图像"), + platform_message.Plain("\\n图像信息:\\n") + ]) + + for i, img in enumerate(images, 1): + if img.url: + reply_chain.append(platform_message.Plain(f"{i}. 网络图像: {img.url}\\n")) + elif img.path: + reply_chain.append(platform_message.Plain(f"{i}. 本地图像: {os.path.basename(img.path)}\\n")) + elif img.base64: + reply_chain.append(platform_message.Plain(f"{i}. Base64 图像\\n")) + + await ctx.reply(reply_chain) + else: + await ctx.reply("请发送图像") + + # 语音功能 + elif text.startswith("!voice"): + await handle_voice_command(ctx, text[6:].strip()) + + # 帮助信息 + elif text.startswith("!help"): + help_text = """Discord 插件命令: + !echo <文本> - 回声功能 + !image - 分析图像信息 + !voice join - 加入语音频道 + !voice leave - 离开语音频道 + !voice play <文件> - 播放音频 + !voice stop - 停止播放 + !voice status - 查看状态 + !help - 显示此帮助""" + await ctx.reply(help_text) + +async def handle_voice_command(ctx: EventContext, command: str): + """处理语音相关命令""" + + adapter = ctx.event.query.adapter + guild_id = get_guild_id_from_context(ctx) + + if command == "join": + try: + channel_id = get_user_voice_channel_id(ctx.event.sender.id, guild_id) + if not channel_id: + await ctx.reply("请先加入语音频道") + return + + voice_client = await adapter.join_voice_channel( + guild_id=guild_id, + channel_id=channel_id, + user_id=ctx.event.sender.id + ) + + await ctx.reply("成功加入语音频道!") + + except Exception as e: + await ctx.reply(f"加入失败: {e}") + + elif command == "leave": + success = await adapter.leave_voice_channel(guild_id) + if success: + await ctx.reply("已离开语音频道") + else: + await ctx.reply("未连接到语音频道") + + elif command.startswith("play"): + audio_file = command[4:].strip() + if not audio_file: + audio_file = "path/to/default/audio.mp3" + + voice_client = await adapter.get_voice_client(guild_id) + if not voice_client: + await ctx.reply("请先加入语音频道") + return + + if voice_client.is_playing(): + await ctx.reply("正在播放其他音频") + return + + try: + import discord + audio_source = discord.FFmpegPCMAudio(audio_file) + voice_client.play(audio_source) + await ctx.reply(f"开始播放: {os.path.basename(audio_file)}") + except Exception as e: + await ctx.reply(f"播放失败: {e}") + + elif command == "stop": + voice_client = await adapter.get_voice_client(guild_id) + if voice_client and voice_client.is_playing(): + voice_client.stop() + await ctx.reply("已停止播放") + else: + await ctx.reply("当前没有播放音频") + + elif command == "status": + status = await adapter.get_voice_connection_status(guild_id) + if status: + status_text = f"""语音状态: + 频道: {status['channel_name']} + 延迟: {status['latency']:.2f}ms + 用户数: {status['user_count']} + 状态: {status['status']}""" + await ctx.reply(status_text) + else: + await ctx.reply("未连接到语音频道") + + else: + await ctx.reply("未知的语音命令,使用 !help 查看帮助") + +# 辅助函数实现(需要根据实际情况调整) +def get_guild_id_from_context(ctx): + """从上下文获取 guild_id""" + # 这里需要根据实际的 LangBot 实现来获取 guild_id + # 可能需要从 ctx.event.query 或其他地方获取 + return ctx.event.query.channel_id # 示例实现 + +def get_user_voice_channel_id(user_id, guild_id): + """获取用户当前所在的语音频道 ID""" + # 这里需要实现获取用户语音频道的逻辑 + # 可能需要通过 Discord API 查询 + return None # 示例实现,实际需要实现具体逻辑 +``` + +## 错误处理最佳实践 + +### 语音错误处理 + +```python +async def safe_voice_operation(adapter, operation, *args, **kwargs): + """安全的语音操作包装器""" + try: + return await operation(*args, **kwargs) + except VoicePermissionError as e: + if "user_not_in_channel" in e.missing_permissions: + return "错误: 用户不在语音频道中" + elif "connect" in e.missing_permissions: + return "错误: 机器人没有连接权限" + elif "speak" in e.missing_permissions: + return "错误: 机器人没有发言权限" + else: + return f"权限错误: {', '.join(e.missing_permissions)}" + except VoiceNetworkError as e: + return f"网络错误: {e}" + except Exception as e: + return f"未知错误: {e}" +``` + +### 图像错误处理 + +```python +async def safe_image_processing(message_chain): + """安全的图像处理""" + processed_images = [] + errors = [] + + for element in message_chain: + if isinstance(element, platform_message.Image): + try: + # 处理图像 + if element.url: + # 处理网络图像 + pass + elif element.path: + # 处理本地图像 + if not os.path.exists(element.path): + errors.append(f"文件不存在: {element.path}") + continue + elif element.base64: + # 处理 Base64 图像 + pass + + processed_images.append(element) + + except Exception as e: + errors.append(f"图像处理失败: {e}") + + return processed_images, errors +``` + +## 性能优化建议 + +### 异步图像下载 + +```python +import aiohttp +import asyncio + +async def download_images_concurrently(image_urls, max_concurrent=5): + """并发下载多张图像""" + semaphore = asyncio.Semaphore(max_concurrent) + + async def download_single(session, url): + async with semaphore: + try: + async with session.get(url) as response: + return await response.read() + except Exception as e: + print(f"下载失败 {url}: {e}") + return None + + async with aiohttp.ClientSession() as session: + tasks = [download_single(session, url) for url in image_urls] + return await asyncio.gather(*tasks) +``` + +### 语音连接复用 + +```python +class VoiceConnectionPool: + """语音连接池管理""" + + def __init__(self, adapter): + self.adapter = adapter + self.connections = {} + + async def get_connection(self, guild_id, channel_id, user_id=None): + """获取或创建语音连接""" + if guild_id in self.connections: + # 检查现有连接是否有效 + if await self.adapter.is_connected_to_voice(guild_id): + return self.connections[guild_id] + + # 创建新连接 + voice_client = await self.adapter.join_voice_channel( + guild_id, channel_id, user_id + ) + self.connections[guild_id] = voice_client + return voice_client + + async def cleanup_invalid_connections(self): + """清理无效连接""" + invalid_guilds = [] + for guild_id in self.connections: + if not await self.adapter.is_connected_to_voice(guild_id): + invalid_guilds.append(guild_id) + + for guild_id in invalid_guilds: + del self.connections[guild_id] +``` + +--- + +> **提示**: 此快速入门基于实际的 discord.py 实现。建议在开发过程中参考 [Discord Developer Documentation](https://discord.com/developers/docs) 了解更多 Discord API 详情。 diff --git a/docs/zh/develop/adapter/discord/troubleshooting.md b/docs/zh/develop/adapter/discord/troubleshooting.md new file mode 100644 index 000000000..6c900070c --- /dev/null +++ b/docs/zh/develop/adapter/discord/troubleshooting.md @@ -0,0 +1,912 @@ +# Discord 适配器故障排除 + +## 概述 + +本文档提供 Discord 适配器常见问题的诊断和解决方案,涵盖连接问题、消息处理、图像功能、语音连接等各个方面。 + +## 连接相关问题 + +### 1. 无法启动 Discord 适配器 + +#### 症状 +- 适配器启动失败 +- 连接超时错误 +- 认证失败 + +#### 可能原因和解决方案 + +##### 1.1 Token 配置错误 + +```python +# 错误信息示例 +discord.errors.LoginFailure: Improper token has been passed. + +# 解决方案 +1. 检查配置文件中的 token 是否正确 +2. 确认 token 没有包含额外的空格或特殊字符 +3. 验证 token 是否仍然有效 + +# 验证 token 的方法 +import aiohttp +import asyncio + +async def verify_token(token): + headers = {'Authorization': f'Bot {token}'} + async with aiohttp.ClientSession() as session: + async with session.get('https://discord.com/api/v10/users/@me', headers=headers) as response: + if response.status == 200: + print("Token 有效") + data = await response.json() + print(f"Bot 用户名: {data['username']}") + else: + print(f"Token 无效: {response.status}") +``` + +##### 1.2 网络连接问题 + +```python +# 错误信息示例 +aiohttp.client_exceptions.ClientConnectorError: Cannot connect to host discord.com + +# 解决方案 +1. 检查网络连接 +2. 配置代理(如果需要) +3. 检查防火墙设置 + +# 代理配置示例 +export http_proxy="http://proxy.example.com:8080" +export https_proxy="http://proxy.example.com:8080" + +# 或在代码中设置 +import os +os.environ['http_proxy'] = 'http://proxy.example.com:8080' +``` + +##### 1.3 权限配置问题 + +```python +# 确保 Bot 具有必要权限 +required_permissions = [ + 'Read Messages', + 'Send Messages', + 'Embed Links', + 'Attach Files', + 'Read Message History' +] + +# 语音功能额外权限 +voice_permissions = [ + 'Connect', + 'Speak', + 'Use Voice Activity' +] +``` + +### 2. 连接频繁断开 + +#### 症状 +- WebSocket 连接频繁中断 +- 消息发送失败 +- 事件接收延迟 + +#### 解决方案 + +```python +# 启用详细日志查看连接状态 +import logging +logging.basicConfig(level=logging.DEBUG) + +# 检查网络稳定性 +async def network_stability_test(): + import aiohttp + import asyncio + + for i in range(10): + try: + async with aiohttp.ClientSession() as session: + start_time = asyncio.get_event_loop().time() + async with session.get('https://discord.com/api/v10/gateway') as response: + end_time = asyncio.get_event_loop().time() + latency = (end_time - start_time) * 1000 + print(f"测试 {i+1}: {response.status}, 延迟: {latency:.2f}ms") + except Exception as e: + print(f"测试 {i+1} 失败: {e}") + + await asyncio.sleep(1) +``` + +## 消息处理问题 + +### 1. 消息发送失败 + +#### 症状 +- 发送消息时抛出异常 +- 消息内容被截断 +- 消息格式错误 + +#### 常见问题和解决方案 + +##### 1.1 消息长度超限 + +```python +# Discord 消息长度限制: 2000 字符 +def check_message_length(text): + if len(text) > 2000: + print(f"消息过长: {len(text)} 字符,需要分割") + return False + return True + +# 自动分割长消息 +def split_long_message(text, max_length=2000): + chunks = [] + while len(text) > max_length: + # 找到合适的分割点 + split_pos = text.rfind('\n', 0, max_length) + if split_pos == -1: + split_pos = text.rfind(' ', 0, max_length) + if split_pos == -1: + split_pos = max_length + + chunks.append(text[:split_pos]) + text = text[split_pos:].lstrip() + + if text: + chunks.append(text) + + return chunks + +# 使用示例 +async def send_long_message(adapter, channel_id, long_text): + chunks = split_long_message(long_text) + for chunk in chunks: + await adapter.send_message("channel", channel_id, MessageChain([Plain(chunk)])) + await asyncio.sleep(0.5) # 避免速率限制 +``` + +##### 1.2 频道权限不足 + +```python +# 检查 Bot 在频道中的权限 +async def check_channel_permissions(bot, channel_id): + try: + channel = bot.get_channel(channel_id) + if not channel: + channel = await bot.fetch_channel(channel_id) + + permissions = channel.permissions_for(channel.guild.me) + + required_perms = { + 'send_messages': permissions.send_messages, + 'embed_links': permissions.embed_links, + 'attach_files': permissions.attach_files, + 'read_messages': permissions.read_messages + } + + print("频道权限检查:") + for perm, has_perm in required_perms.items(): + status = "✓" if has_perm else "✗" + print(f" {status} {perm}: {has_perm}") + + return all(required_perms.values()) + + except Exception as e: + print(f"权限检查失败: {e}") + return False +``` + +### 2. 消息接收问题 + +#### 症状 +- 收不到用户消息 +- 事件处理延迟 +- 消息内容缺失 + +#### 解决方案 + +```python +# 检查 Intents 配置 +import discord + +# 确保启用了必要的 intents +intents = discord.Intents.default() +intents.message_content = True # 必须启用才能读取消息内容 +intents.guild_messages = True +intents.dm_messages = True + +# 在 Discord Developer Portal 中也需要启用对应的 Privileged Gateway Intents + +# 调试消息接收 +class DebugClient(discord.Client): + async def on_message(self, message): + print(f"收到消息: {message.author} -> {message.content}") + if message.author.bot: + print("跳过 Bot 消息") + return + + # 处理消息... +``` + +## 图像处理问题 + +### 1. 图像发送失败 + +#### 症状 +- 图像无法上传 +- 图像格式错误 +- 文件大小超限 + +#### 解决方案 + +##### 1.1 文件大小检查 + +```python +import os + +def check_image_size(image_path, max_size=25*1024*1024): # 25MB 限制 + """检查图像文件大小""" + if not os.path.exists(image_path): + return False, "文件不存在" + + size = os.path.getsize(image_path) + if size > max_size: + return False, f"文件过大: {size/1024/1024:.2f}MB, 限制: {max_size/1024/1024}MB" + + return True, "文件大小正常" + +# 图像压缩示例 +from PIL import Image +import io + +def compress_image(image_path, max_size=1024, quality=85): + """压缩图像以减小文件大小""" + try: + with Image.open(image_path) as img: + # 调整尺寸 + if max(img.size) > max_size: + ratio = max_size / max(img.size) + new_size = tuple(int(dim * ratio) for dim in img.size) + img = img.resize(new_size, Image.Resampling.LANCZOS) + + # 保存为 JPEG 以减小文件大小 + output = io.BytesIO() + img.convert('RGB').save(output, format='JPEG', quality=quality, optimize=True) + return output.getvalue() + + except Exception as e: + print(f"图像压缩失败: {e}") + return None +``` + +##### 1.2 格式检测和转换 + +```python +def detect_image_format(image_data): + """检测图像格式""" + if image_data.startswith(b'\xff\xd8\xff'): + return 'jpeg' + elif image_data.startswith(b'\x89PNG\r\n\x1a\n'): + return 'png' + elif image_data.startswith(b'GIF87a') or image_data.startswith(b'GIF89a'): + return 'gif' + elif image_data.startswith(b'RIFF') and b'WEBP' in image_data[:20]: + return 'webp' + else: + return 'unknown' + +# 安全的图像读取 +async def safe_image_processing(image_element): + """安全地处理图像元素""" + try: + image_data = None + filename = f'{uuid.uuid4()}.png' + + if image_element.base64: + # 处理 Base64 图像 + if image_element.base64.startswith('data:'): + base64_data = image_element.base64.split(',')[1] + else: + base64_data = image_element.base64 + + image_data = base64.b64decode(base64_data) + + elif image_element.url: + # 下载网络图像 + async with aiohttp.ClientSession() as session: + async with session.get(image_element.url, timeout=30) as response: + if response.status == 200: + image_data = await response.read() + else: + raise Exception(f"下载失败: HTTP {response.status}") + + elif image_element.path: + # 读取本地文件 + clean_path = os.path.abspath(image_element.path.replace('\x00', '')) + if os.path.exists(clean_path): + with open(clean_path, 'rb') as f: + image_data = f.read() + else: + raise Exception(f"文件不存在: {clean_path}") + + if image_data: + # 检测格式并设置文件名 + format_name = detect_image_format(image_data) + if format_name != 'unknown': + filename = f'{uuid.uuid4()}.{format_name}' + + # 检查文件大小 + if len(image_data) > 25*1024*1024: # 25MB + raise Exception(f"图像过大: {len(image_data)/1024/1024:.2f}MB") + + return discord.File(fp=io.BytesIO(image_data), filename=filename) + + except Exception as e: + print(f"图像处理失败: {e}") + return None +``` + +### 2. 网络图像下载问题 + +#### 症状 +- 图像下载超时 +- 网络连接错误 +- 图像内容损坏 + +#### 解决方案 + +```python +async def robust_image_download(url, max_retries=3, timeout=30): + """健壮的图像下载""" + + for attempt in range(max_retries): + try: + async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=timeout)) as session: + async with session.get(url, headers={'User-Agent': 'LangBot/1.0'}) as response: + if response.status == 200: + content_length = response.headers.get('Content-Length') + if content_length and int(content_length) > 25*1024*1024: + raise Exception("图像文件过大") + + image_data = await response.read() + + # 验证图像数据 + if len(image_data) < 100: # 太小可能不是有效图像 + raise Exception("图像数据太小") + + # 检查格式 + if detect_image_format(image_data) == 'unknown': + raise Exception("不支持的图像格式") + + return image_data + else: + raise Exception(f"HTTP错误: {response.status}") + + except asyncio.TimeoutError: + print(f"下载超时 (尝试 {attempt + 1}/{max_retries}): {url}") + except Exception as e: + print(f"下载失败 (尝试 {attempt + 1}/{max_retries}): {e}") + + if attempt < max_retries - 1: + await asyncio.sleep(2 ** attempt) # 指数退避 + + raise Exception(f"下载失败,已重试 {max_retries} 次") +``` + +## 语音功能问题 + +### 1. 语音连接失败 + +#### 症状 +- 无法加入语音频道 +- 连接超时 +- 权限错误 + +#### 解决方案 + +##### 1.1 依赖检查 + +```python +def check_voice_dependencies(): + """检查语音功能依赖""" + try: + import discord.voice_client + print("✓ discord.py 语音支持正常") + except ImportError: + print("✗ discord.py 语音支持未安装,请运行: pip install discord.py[voice]") + return False + + try: + import nacl + print("✓ PyNaCl 已安装") + except ImportError: + print("✗ PyNaCl 未安装,请运行: pip install PyNaCl") + return False + + # 检查 FFmpeg + import shutil + if shutil.which('ffmpeg'): + print("✓ FFmpeg 已安装") + else: + print("✗ FFmpeg 未安装,请安装 FFmpeg") + return False + + try: + import discord + if not discord.opus.is_loaded(): + discord.opus.load_opus() + print("✓ Opus 编码器已加载") + except Exception as e: + print(f"✗ Opus 编码器加载失败: {e}") + return False + + return True +``` + +##### 1.2 权限诊断 + +```python +async def diagnose_voice_permissions(bot, guild_id, channel_id, user_id): + """诊断语音权限问题""" + try: + guild = bot.get_guild(guild_id) + if not guild: + return "错误: 找不到服务器" + + channel = guild.get_channel(channel_id) + if not channel or not isinstance(channel, discord.VoiceChannel): + return "错误: 找不到语音频道" + + # 检查用户是否在频道中 + member = guild.get_member(user_id) + if not member: + return "错误: 找不到用户" + + if not member.voice or member.voice.channel != channel: + return "错误: 用户不在指定语音频道中" + + # 检查 Bot 权限 + bot_member = guild.me + permissions = channel.permissions_for(bot_member) + + missing_perms = [] + if not permissions.connect: + missing_perms.append("Connect") + if not permissions.speak: + missing_perms.append("Speak") + if not permissions.use_voice_activation: + missing_perms.append("Use Voice Activity") + + if missing_perms: + return f"权限不足: {', '.join(missing_perms)}" + + return "权限检查通过" + + except Exception as e: + return f"权限检查失败: {e}" +``` + +### 2. 音频播放问题 + +#### 症状 +- 音频无法播放 +- 播放中断 +- 音质问题 + +#### 解决方案 + +##### 2.1 音频文件检查 + +```python +def check_audio_file(file_path): + """检查音频文件有效性""" + import os + import subprocess + + if not os.path.exists(file_path): + return False, "文件不存在" + + # 使用 FFmpeg 检查文件 + try: + result = subprocess.run([ + 'ffprobe', '-v', 'quiet', '-print_format', 'json', + '-show_format', '-show_streams', file_path + ], capture_output=True, text=True, timeout=10) + + if result.returncode != 0: + return False, "不是有效的音频文件" + + import json + info = json.loads(result.stdout) + + # 检查是否有音频流 + audio_streams = [s for s in info['streams'] if s['codec_type'] == 'audio'] + if not audio_streams: + return False, "文件中没有音频流" + + return True, f"音频文件正常 ({info['format']['format_name']})" + + except subprocess.TimeoutExpired: + return False, "文件检查超时" + except Exception as e: + return False, f"文件检查失败: {e}" +``` + +##### 2.2 音频播放优化 + +```python +async def play_audio_with_error_handling(voice_client, audio_path): + """带错误处理的音频播放""" + + if not voice_client or not voice_client.is_connected(): + raise Exception("语音客户端未连接") + + if voice_client.is_playing(): + voice_client.stop() + await asyncio.sleep(0.5) # 等待停止完成 + + # 检查音频文件 + is_valid, message = check_audio_file(audio_path) + if not is_valid: + raise Exception(f"音频文件无效: {message}") + + try: + # 优化的 FFmpeg 选项 + ffmpeg_options = { + 'before_options': ( + '-loglevel quiet ' + '-reconnect 1 ' + '-reconnect_streamed 1 ' + '-reconnect_delay_max 5' + ), + 'options': ( + '-vn ' # 禁用视频 + '-filter:a "volume=0.5" ' # 音量控制 + '-ac 2 ' # 立体声 + '-ar 48000 ' # 采样率 + '-f s16le' # 输出格式 + ) + } + + audio_source = discord.FFmpegPCMAudio(audio_path, **ffmpeg_options) + + # 播放音频 + voice_client.play(audio_source) + + # 监控播放状态 + while voice_client.is_playing(): + await asyncio.sleep(1) + + print("音频播放完成") + + except Exception as e: + print(f"音频播放失败: {e}") + raise +``` + +### 3. 连接稳定性问题 + +#### 症状 +- 连接频繁断开 +- 延迟过高 +- 音频卡顿 + +#### 解决方案 + +```python +async def monitor_voice_connection(adapter, guild_id, monitoring_duration=60): + """监控语音连接质量""" + + start_time = asyncio.get_event_loop().time() + latencies = [] + disconnection_count = 0 + + while asyncio.get_event_loop().time() - start_time < monitoring_duration: + try: + status = await adapter.get_voice_connection_status(guild_id) + + if status: + latencies.append(status['latency']) + print(f"延迟: {status['latency']:.2f}ms, 健康度: {status['connection_health']}") + else: + disconnection_count += 1 + print("连接已断开") + + # 尝试重连 + if disconnection_count < 3: + print("尝试重连...") + # 这里需要实现重连逻辑 + + except Exception as e: + print(f"监控错误: {e}") + + await asyncio.sleep(5) + + if latencies: + avg_latency = sum(latencies) / len(latencies) + max_latency = max(latencies) + print(f"平均延迟: {avg_latency:.2f}ms, 最大延迟: {max_latency:.2f}ms") + print(f"断开次数: {disconnection_count}") +``` + +## 性能问题 + +### 1. 内存使用过高 + +#### 症状 +- 内存占用持续增长 +- 程序响应变慢 +- 内存溢出错误 + +#### 解决方案 + +```python +import gc +import psutil +import os + +def monitor_memory_usage(): + """监控内存使用情况""" + process = psutil.Process(os.getpid()) + memory_info = process.memory_info() + memory_mb = memory_info.rss / 1024 / 1024 + + print(f"内存使用: {memory_mb:.2f} MB") + return memory_mb + +async def cleanup_resources(adapter): + """清理资源""" + # 清理语音连接 + if hasattr(adapter, 'voice_manager') and adapter.voice_manager: + await adapter.voice_manager.cleanup_inactive_connections() + + # 强制垃圾回收 + gc.collect() + + print(f"清理后内存使用: {monitor_memory_usage():.2f} MB") + +# 定期内存监控 +async def memory_monitor_task(adapter, interval=300): # 5分钟检查一次 + """内存监控任务""" + while True: + memory_usage = monitor_memory_usage() + + if memory_usage > 500: # 500MB 阈值 + print("内存使用过高,执行清理...") + await cleanup_resources(adapter) + + await asyncio.sleep(interval) +``` + +### 2. 连接数过多 + +#### 症状 +- 达到连接限制 +- 新连接失败 +- 性能下降 + +#### 解决方案 + +```python +class ConnectionLimiter: + """连接数限制器""" + + def __init__(self, max_connections=10): + self.max_connections = max_connections + self.active_connections = {} + + async def acquire_connection(self, guild_id, adapter): + """获取连接""" + if len(self.active_connections) >= self.max_connections: + # 断开最旧的连接 + oldest_guild = min(self.active_connections.keys(), + key=lambda g: self.active_connections[g]['created_at']) + + await adapter.leave_voice_channel(oldest_guild) + del self.active_connections[oldest_guild] + print(f"断开最旧连接: {oldest_guild}") + + self.active_connections[guild_id] = { + 'created_at': asyncio.get_event_loop().time(), + 'last_activity': asyncio.get_event_loop().time() + } + + def release_connection(self, guild_id): + """释放连接""" + if guild_id in self.active_connections: + del self.active_connections[guild_id] + + def update_activity(self, guild_id): + """更新活动时间""" + if guild_id in self.active_connections: + self.active_connections[guild_id]['last_activity'] = asyncio.get_event_loop().time() +``` + +## 调试工具 + +### 1. 详细日志配置 + +```python +import logging +import sys + +def setup_detailed_logging(): + """设置详细日志""" + + # 创建格式化器 + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s' + ) + + # 控制台处理器 + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setFormatter(formatter) + console_handler.setLevel(logging.INFO) + + # 文件处理器 + file_handler = logging.FileHandler('discord_debug.log', encoding='utf-8') + file_handler.setFormatter(formatter) + file_handler.setLevel(logging.DEBUG) + + # Discord.py 日志 + discord_logger = logging.getLogger('discord') + discord_logger.setLevel(logging.DEBUG) + discord_logger.addHandler(console_handler) + discord_logger.addHandler(file_handler) + + # 适配器日志 + adapter_logger = logging.getLogger('discord_adapter') + adapter_logger.setLevel(logging.DEBUG) + adapter_logger.addHandler(console_handler) + adapter_logger.addHandler(file_handler) + + return discord_logger, adapter_logger +``` + +### 2. 连接诊断工具 + +```python +async def comprehensive_diagnostic(adapter): + """综合诊断工具""" + + print("=== Discord 适配器诊断 ===") + + # 1. 基础连接检查 + print("1. 基础连接状态:") + if hasattr(adapter, 'bot') and adapter.bot.is_ready(): + print(" ✓ Discord 连接正常") + print(f" ✓ Bot 用户: {adapter.bot.user}") + print(f" ✓ 延迟: {adapter.bot.latency*1000:.2f}ms") + else: + print(" ✗ Discord 连接异常") + + # 2. 语音功能检查 + print("\\n2. 语音功能状态:") + voice_deps_ok = check_voice_dependencies() + if voice_deps_ok: + print(" ✓ 语音依赖正常") + else: + print(" ✗ 语音依赖异常") + + if hasattr(adapter, 'voice_manager') and adapter.voice_manager: + connections = await adapter.list_active_voice_connections() + print(f" ✓ 语音管理器正常,活跃连接: {len(connections)}") + for conn in connections: + print(f" - {conn['guild_id']}: {conn['channel_name']} ({conn['latency']:.2f}ms)") + else: + print(" ✗ 语音管理器未初始化") + + # 3. 内存使用检查 + print("\\n3. 资源使用状态:") + memory_usage = monitor_memory_usage() + print(f" 内存使用: {memory_usage:.2f} MB") + + # 4. 网络连接测试 + print("\\n4. 网络连接测试:") + await network_stability_test() + + print("\\n=== 诊断完成 ===") +``` + +### 3. 实时监控面板 + +```python +import asyncio +import time + +class DiscordMonitor: + """Discord 适配器实时监控""" + + def __init__(self, adapter): + self.adapter = adapter + self.stats = { + 'messages_sent': 0, + 'messages_received': 0, + 'voice_connections': 0, + 'errors': 0, + 'start_time': time.time() + } + + async def start_monitoring(self): + """启动监控""" + while True: + await self.display_status() + await asyncio.sleep(10) + + async def display_status(self): + """显示状态信息""" + uptime = time.time() - self.stats['start_time'] + uptime_str = f"{int(uptime//3600)}h {int((uptime%3600)//60)}m {int(uptime%60)}s" + + print("\\n" + "="*50) + print(f"Discord 适配器监控 - 运行时间: {uptime_str}") + print("="*50) + + # 基础状态 + if hasattr(self.adapter, 'bot') and self.adapter.bot.is_ready(): + print(f"连接状态: ✓ 正常 (延迟: {self.adapter.bot.latency*1000:.2f}ms)") + else: + print("连接状态: ✗ 异常") + + # 统计信息 + print(f"消息发送: {self.stats['messages_sent']}") + print(f"消息接收: {self.stats['messages_received']}") + print(f"语音连接: {self.stats['voice_connections']}") + print(f"错误次数: {self.stats['errors']}") + + # 内存使用 + memory = monitor_memory_usage() + print(f"内存使用: {memory:.2f} MB") + + # 语音连接详情 + if hasattr(self.adapter, 'voice_manager') and self.adapter.voice_manager: + connections = await self.adapter.list_active_voice_connections() + if connections: + print("\\n语音连接:") + for conn in connections: + print(f" {conn['guild_id']}: {conn['channel_name']} " + f"({conn['user_count']} 用户, {conn['latency']:.2f}ms)") + + print("="*50) + + def record_message_sent(self): + self.stats['messages_sent'] += 1 + + def record_message_received(self): + self.stats['messages_received'] += 1 + + def record_voice_connection(self): + self.stats['voice_connections'] += 1 + + def record_error(self): + self.stats['errors'] += 1 + +# 使用示例 +async def run_with_monitoring(adapter): + monitor = DiscordMonitor(adapter) + + # 启动监控任务 + monitor_task = asyncio.create_task(monitor.start_monitoring()) + + try: + # 运行适配器 + await adapter.run_async() + finally: + monitor_task.cancel() +``` + +## 常见问题速查表 + +| 问题类型 | 症状 | 快速检查 | 解决方案 | +|---------|------|----------|----------| +| 连接失败 | LoginFailure | 检查 token | 验证并更新 token | +| 网络超时 | ClientConnectorError | 检查网络 | 配置代理或检查防火墙 | +| 权限不足 | Forbidden 403 | 检查 Bot 权限 | 在 Discord 中添加权限 | +| 消息过长 | 消息被截断 | 检查字符数 | 分割长消息 | +| 图像失败 | 文件上传错误 | 检查文件大小/格式 | 压缩或转换格式 | +| 语音连接失败 | VoiceConnectionError | 检查依赖和权限 | 安装依赖,检查频道权限 | +| 音频播放失败 | FFmpeg 错误 | 检查 FFmpeg 和文件 | 安装 FFmpeg,验证音频文件 | +| 内存泄漏 | 内存持续增长 | 监控内存使用 | 清理连接,执行 GC | +| 高延迟 | 响应慢 | 检查网络延迟 | 优化网络,减少并发 | + +--- + +> **提示**: 遇到问题时,首先启用详细日志记录,然后使用诊断工具进行系统性排查。大多数问题都与配置、权限或网络环境相关。 diff --git a/package-lock.json b/package-lock.json index c69147e9d..52cc52d7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,9 @@ "requires": true, "packages": { "": { + "dependencies": { + "mermaid": "^11.8.0" + }, "devDependencies": { "vitepress": "^1.3.4" } @@ -304,6 +307,28 @@ "@algolia/requester-common": "4.24.0" } }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/utils": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.1.tgz", + "integrity": "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==", + "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", @@ -351,6 +376,51 @@ "node": ">=6.9.0" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", + "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==", + "license": "MIT" + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "11.0.3", + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "license": "Apache-2.0" + }, "node_modules/@docsearch/css": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.1.tgz", @@ -767,12 +837,43 @@ "node": ">=12" } }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.3.0.tgz", + "integrity": "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.0.0", + "@antfu/utils": "^8.1.0", + "@iconify/types": "^2.0.0", + "debug": "^4.4.0", + "globals": "^15.14.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.0.0", + "mlly": "^1.7.4" + } + }, "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==", "dev": true }, + "node_modules/@mermaid-js/parser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.0.tgz", + "integrity": "sha512-7DNESgpyZ5WG1SIkrYafVBhWmImtmQuoxOO1lawI3gQYWxBX3v1FW3IyuuRfKJAO06XrZR71W0Kif5VEGGd4VA==", + "license": "MIT", + "dependencies": { + "langium": "3.3.1" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.27.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz", @@ -1025,12 +1126,271 @@ "shiki": "1.14.1" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "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==", "dev": true }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -1062,6 +1422,13 @@ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "dev": true }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -1400,6 +1767,18 @@ } } }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/algoliasearch": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.24.0.tgz", @@ -1471,6 +1850,47 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "license": "MIT", + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "license": "MIT" + }, "node_modules/copy-anything": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", @@ -1486,12 +1906,561 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "dev": true }, + "node_modules/cytoscape": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.32.0.tgz", + "integrity": "sha512-5JHBC9n75kz5851jeklCPmZWcg3hUe6sjqJvyk3+hVqFaKcHwHgxsjeN1yLmggoUc6STbtm9/NQyabQehfjvWQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.11.tgz", + "integrity": "sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/dompurify": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -1548,6 +2517,12 @@ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "license": "MIT" + }, "node_modules/focus-trap": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", @@ -1571,12 +2546,51 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, "node_modules/hookable": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", "dev": true }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-what": { "version": "4.1.16", "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", @@ -1589,6 +2603,87 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/katex": { + "version": "0.16.22", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", + "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "license": "MIT" + }, + "node_modules/langium": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", + "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", + "license": "MIT", + "dependencies": { + "chevrotain": "~11.0.3", + "chevrotain-allstar": "~0.3.0", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.0.8" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.1.tgz", + "integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.0.1", + "quansync": "^0.2.8" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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/magic-string": { "version": "0.30.11", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", @@ -1604,6 +2699,46 @@ "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", "dev": true }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mermaid": { + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.8.0.tgz", + "integrity": "sha512-uAZUwnBiqREZcUrFw3G5iQ5Pj3hTYUP95EZc3ec/nGBzHddJZydzYGE09tGZDBS1VoSoDn0symZ85FmypSTo5g==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.0.4", + "@iconify/utils": "^2.1.33", + "@mermaid-js/parser": "^0.6.0", + "@types/d3": "^7.4.3", + "cytoscape": "^3.29.3", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.11", + "dayjs": "^1.11.13", + "dompurify": "^3.2.5", + "katex": "^0.16.9", + "khroma": "^2.1.0", + "lodash-es": "^4.17.21", + "marked": "^15.0.7", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0" + } + }, "node_modules/minisearch": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.0.tgz", @@ -1616,10 +2751,45 @@ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true }, + "node_modules/mlly": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", + "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "pathe": "^2.0.1", + "pkg-types": "^1.3.0", + "ufo": "^1.5.4" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "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==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -1627,6 +2797,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -1634,6 +2805,24 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/package-manager-detector": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.3.0.tgz", + "integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==", + "license": "MIT" + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -1646,6 +2835,33 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, + "node_modules/pkg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.2.0.tgz", + "integrity": "sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/postcss": { "version": "8.4.49", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", @@ -1684,12 +2900,34 @@ "url": "https://opencollective.com/preact" } }, + "node_modules/quansync": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.10.tgz", + "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/rfdc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, "node_modules/rollup": { "version": "4.27.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.4.tgz", @@ -1727,6 +2965,30 @@ "fsevents": "~2.3.2" } }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/search-insights": { "version": "2.17.0", "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.0.tgz", @@ -1762,6 +3024,12 @@ "node": ">=0.10.0" } }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, "node_modules/superjson": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.1.tgz", @@ -1780,6 +3048,12 @@ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", "dev": true }, + "node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "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", @@ -1789,11 +3063,40 @@ "node": ">=4" } }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -1887,6 +3190,55 @@ } } }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "license": "MIT" + }, "node_modules/vue": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.38.tgz", diff --git a/package.json b/package.json index 253bbadea..2fe634de7 100644 --- a/package.json +++ b/package.json @@ -6,5 +6,8 @@ "docs:dev": "vitepress dev docs", "docs:build": "vitepress build docs", "docs:preview": "vitepress preview docs" + }, + "dependencies": { + "mermaid": "^11.8.0" } -} \ No newline at end of file +}