diff --git a/.env b/.env index 3e50054..5f1b1ef 100644 --- a/.env +++ b/.env @@ -8,4 +8,9 @@ REGEX_FILE=/data/regex_rules.json # ---------- 服务地址 ---------- COMFYUI_API_URL=http://comfyui:8188 BACKEND_PORT=8000 -FRONTEND_PORT=8501 \ No newline at end of file +FRONTEND_PORT=8501 + +# 先配置 .env 文件 +MAIN_LLM_API_KEY=sk-Oh4o3fzV6Qe59B6DRwSskE48xe5D6bq1hkgDZqH1mmJOCN8j +MAIN_LLM_BASE_URL=https://api.chatfire.cn/v1 +MAIN_LLM_MODEL=glm4.7 diff --git a/.gitignore b/.gitignore index 160f936..3aa3495 100644 Binary files a/.gitignore and b/.gitignore differ diff --git a/.idea/llm-workflow-engine.iml b/.idea/llm-workflow-engine.iml index c1e4429..b28b586 100644 --- a/.idea/llm-workflow-engine.iml +++ b/.idea/llm-workflow-engine.iml @@ -3,6 +3,7 @@ + diff --git a/backend/Dockerfile b/backend/Dockerfile index 354240c..beeccf3 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -4,6 +4,10 @@ FROM python:3.11-slim # 设置工作目录 WORKDIR /app +# 设置环境变量 +ENV PYTHONUNBUFFERED=1 +ENV PYTHONDONTWRITEBYTECODE=1 + # 复制依赖文件 COPY requirements.txt . @@ -11,13 +15,10 @@ COPY requirements.txt . RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt # 复制所有代码 -# 修改点:把当前目录(即 backend/)的内容复制到 /app/backend/ 下 -# 这样镜像内的结构就是 /app/backend/api/route.py -COPY . /app/backend/ +COPY . . # 暴露端口 EXPOSE 8000 # 启动命令 -# 修改点:路径改为 backend.api.route (对应 /app/backend/api/route.py) -CMD ["uvicorn", "backend.api.route:app", "--host", "0.0.0.0", "--port", "8000"] +CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/backend/api/route.py b/backend/api/route.py index d458a1c..e71f88e 100644 --- a/backend/api/route.py +++ b/backend/api/route.py @@ -1,5 +1,8 @@ from fastapi import APIRouter from .routes import presetsRoute, chatsRoute, worldbooksRoute +from utils.file_utils import get_all_roles_and_chats +from core.config import settings +from pathlib import Path router = APIRouter() @@ -12,5 +15,4 @@ router.include_router(worldbooksRoute.router) # 保留原有的其他路由 @router.get("/tool_bar/get_all_role_and_chat") def get_all_role_and_chat_endpoint(): - from ..tools.get_all_role_and_chat import get_all_role_and_chat - return get_all_role_and_chat() + return get_all_roles_and_chats(Path(settings.DATA_PATH)) diff --git a/backend/api/routes/chatsRoute.py b/backend/api/routes/chatsRoute.py index 98f8d6e..855b3da 100644 --- a/backend/api/routes/chatsRoute.py +++ b/backend/api/routes/chatsRoute.py @@ -1,97 +1,56 @@ from fastapi import APIRouter, HTTPException, status -from backend.core.models.chat_history import ChatHistory, Message +# TODO: 实现 ChatService 来替代旧的 ChatHistory 逻辑 +# from services.chat_service import ChatService router = APIRouter(prefix="/chat", tags=["chat"]) - -# ========== 聊天历史基础路由 ========== - @router.get("", response_model=dict) async def list_all_chats(): """获取所有角色的所有聊天列表""" - return await ChatHistory.list_all_chats() - + # return await ChatService.list_all_chats() + return {"chats": []} @router.get("/{role_name}/{chat_name}") async def get_chat(role_name: str, chat_name: str): """获取指定聊天的完整内容""" - try: - return await ChatHistory.get_chat(role_name, chat_name) - except FileNotFoundError as e: - raise HTTPException(status_code=404, detail="Chat not found") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.post("/{role_name}", status_code=status.HTTP_201_CREATED) async def create_chat(role_name: str, chat_name: str, metadata: dict = None): """创建新聊天""" - try: - return await ChatHistory.create_chat(role_name, chat_name, metadata) - except FileExistsError: - raise HTTPException(status_code=400, detail="Chat already exists") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.put("/{role_name}/{chat_name}") async def update_chat(role_name: str, chat_name: str, update_data: dict): """更新聊天元数据""" - try: - return await ChatHistory.update_chat(role_name, chat_name, update_data) - except FileNotFoundError: - raise HTTPException(status_code=404, detail="Chat not found") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.delete("/{role_name}/{chat_name}") async def delete_chat(role_name: str, chat_name: str): """删除指定聊天""" - try: - return await ChatHistory.delete_chat(role_name, chat_name) - except FileNotFoundError: - raise HTTPException(status_code=404, detail="Chat not found") - - -# ========== 聊天消息路由 ========== + raise HTTPException(status_code=501, detail="Not Implemented") @router.get("/{role_name}/{chat_name}/messages") async def list_messages(role_name: str, chat_name: str): """获取聊天的所有消息""" - try: - return await ChatHistory.list_messages(role_name, chat_name) - except FileNotFoundError: - raise HTTPException(status_code=404, detail="Chat not found") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.get("/{role_name}/{chat_name}/messages/{floor}") async def get_message(role_name: str, chat_name: str, floor: int): """获取指定楼层的消息""" - try: - return await ChatHistory.get_message(role_name, chat_name, floor) - except FileNotFoundError: - raise HTTPException(status_code=404, detail="Chat not found") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.post("/{role_name}/{chat_name}/messages", status_code=status.HTTP_201_CREATED) async def add_message(role_name: str, chat_name: str, message_data: dict): """向聊天添加新消息""" - try: - return await ChatHistory.add_message(role_name, chat_name, message_data) - except FileNotFoundError: - raise HTTPException(status_code=404, detail="Chat not found") - except ValueError as e: - raise HTTPException(status_code=400, detail=str(e)) - + raise HTTPException(status_code=501, detail="Not Implemented") @router.put("/{role_name}/{chat_name}/messages/{floor}") async def update_message(role_name: str, chat_name: str, floor: int, update_data: dict): """更新指定楼层的消息""" - try: - return await ChatHistory.update_message(role_name, chat_name, floor, update_data) - except FileNotFoundError: - raise HTTPException(status_code=404, detail="Chat not found") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.delete("/{role_name}/{chat_name}/messages/{floor}") async def delete_message(role_name: str, chat_name: str, floor: int): """删除指定楼层的消息""" - try: - return await ChatHistory.delete_message(role_name, chat_name, floor) - except FileNotFoundError: - raise HTTPException(status_code=404, detail="Chat not found") + raise HTTPException(status_code=501, detail="Not Implemented") diff --git a/backend/api/routes/presetsRoute.py b/backend/api/routes/presetsRoute.py index a5770f6..f125b69 100644 --- a/backend/api/routes/presetsRoute.py +++ b/backend/api/routes/presetsRoute.py @@ -1,98 +1,60 @@ from fastapi import APIRouter, HTTPException, status -from backend.core.models.PromptList import AIDesignSpec -from backend.core.models.PromptComponent import PromptComponent +# TODO: 实现 PresetService 来替代旧的 AIDesignSpec 逻辑 +# from services.preset_service import PresetService router = APIRouter(prefix="/presets", tags=["presets"]) - -# ========== 预设基础路由 ========== - @router.get("", response_model=dict) async def list_presets(): """获取所有预设列表及其基本信息""" - return await AIDesignSpec.list_all_presets() - + # return await PresetService.list_all_presets() + return {"presets": []} @router.get("/{preset_name}") async def get_preset(preset_name: str): """获取指定预设的完整内容""" - try: - return await AIDesignSpec.get_preset(preset_name) - except FileNotFoundError: - raise HTTPException(status_code=404, detail="Preset not found") - + # try: + # return await PresetService.get_preset(preset_name) + # except FileNotFoundError: + # raise HTTPException(status_code=404, detail="Preset not found") + raise HTTPException(status_code=501, detail="Not Implemented") @router.post("", status_code=status.HTTP_201_CREATED) async def create_preset(preset_name: str, preset_data: dict): """创建新预设""" - try: - return await AIDesignSpec.create_preset(preset_name, preset_data) - except FileExistsError: - raise HTTPException(status_code=400, detail="Preset already exists") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.put("/{preset_name}") async def update_preset(preset_name: str, update_data: dict): """更新预设配置""" - try: - return await AIDesignSpec.update_preset(preset_name, update_data) - except FileNotFoundError: - raise HTTPException(status_code=404, detail="Preset not found") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.delete("/{preset_name}") async def delete_preset(preset_name: str): """删除指定预设""" - try: - return await AIDesignSpec.delete_preset(preset_name) - except FileNotFoundError: - raise HTTPException(status_code=404, detail="Preset not found") - - -# ========== 预设组件路由 ========== + raise HTTPException(status_code=501, detail="Not Implemented") @router.get("/{preset_name}/components") async def list_preset_components(preset_name: str): """获取预设中的所有组件""" - try: - return await AIDesignSpec.list_components(preset_name) - except FileNotFoundError: - raise HTTPException(status_code=404, detail="Preset not found") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.get("/{preset_name}/components/{component_id}") async def get_preset_component(preset_name: str, component_id: str): """获取指定组件的详情""" - try: - return await AIDesignSpec.get_component(preset_name, component_id) - except FileNotFoundError: - raise HTTPException(status_code=404, detail="Preset not found") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.post("/{preset_name}/components", status_code=status.HTTP_201_CREATED) async def add_preset_component(preset_name: str, component_data: dict): """向预设添加新组件""" - try: - return await AIDesignSpec.add_component_to_preset(preset_name, component_data) - except FileNotFoundError: - raise HTTPException(status_code=404, detail="Preset not found") - except ValueError as e: - raise HTTPException(status_code=400, detail=str(e)) - + raise HTTPException(status_code=501, detail="Not Implemented") @router.put("/{preset_name}/components/{component_id}") async def update_preset_component(preset_name: str, component_id: str, update_data: dict): """更新指定组件""" - try: - return await AIDesignSpec.update_component_in_preset(preset_name, component_id, update_data) - except FileNotFoundError: - raise HTTPException(status_code=404, detail="Preset not found") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.delete("/{preset_name}/components/{component_id}") async def delete_preset_component(preset_name: str, component_id: str): """从预设中删除指定组件""" - try: - return await AIDesignSpec.delete_component_from_preset(preset_name, component_id) - except FileNotFoundError: - raise HTTPException(status_code=404, detail="Preset not found") + raise HTTPException(status_code=501, detail="Not Implemented") diff --git a/backend/api/routes/worldbooksRoute.py b/backend/api/routes/worldbooksRoute.py index 7b5878e..17d8b3c 100644 --- a/backend/api/routes/worldbooksRoute.py +++ b/backend/api/routes/worldbooksRoute.py @@ -1,4 +1,4 @@ -# 标准库导入 +3# 标准库导入 import os import shutil import logging @@ -10,18 +10,8 @@ from fastapi import APIRouter, HTTPException, UploadFile, File, Form from fastapi.responses import JSONResponse, FileResponse # 本地模块导入 -# 本地模块导入 -from backend.core.models.WorldBook import WorldBook -from backend.core.models.WorldItem import ( - WorldItem, - TriggerConfig, - KeywordTriggerConfig, - RAGTriggerConfig, - ConditionTriggerConfig, - TriggerStrategy -) - -from backend.core.config import settings +from models.internal import WorldInfo, WorldInfoEntry +from core.config import settings # 配置日志 logger = logging.getLogger(__name__) @@ -40,58 +30,15 @@ async def list_worldbooks(): Returns: List[Dict[str, Any]]: 世界书列表 """ - try: - worldbooks = [] - search_dir = settings.WORLDBOOKS_PATH - - # 检查目录是否存在 - if not os.path.exists(search_dir): - logger.warning(f"目录不存在: {search_dir}") - return [] - - for filename in os.listdir(search_dir): - if filename.endswith(".json"): - file_path = os.path.join(search_dir, filename) - try: - # 加载世界书基本信息 - # 传入文件名(不带扩展名) - world_book = WorldBook.load(Path(file_path).stem) - worldbooks.append(world_book.to_summary_dict()) - except Exception as e: - logger.warning(f"加载世界书 {filename} 失败: {str(e)}") - continue - - logger.info(f"获取世界书列表: 共 {len(worldbooks)} 个") - return worldbooks - except Exception as e: - logger.error(f"获取世界书列表失败: {str(e)}") - raise HTTPException(status_code=500, detail=f"获取世界书列表失败: {str(e)}") - + # TODO: 实现 WorldBookService + return [] @router.get("/{name}", response_model=Dict[str, Any]) async def get_worldbook(name: str): """ 获取指定名称的世界书 - - Args: - name: 世界书名称 - - Returns: - Dict[str, Any]: 世界书数据 """ - try: - if not WorldBook.exists(name): - raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在") - - world_book = WorldBook.load(name) - logger.info(f"获取世界书: {name}") - return world_book.to_dict() - except HTTPException: - raise - except Exception as e: - logger.error(f"获取世界书 {name} 失败: {str(e)}") - raise HTTPException(status_code=500, detail=f"获取世界书失败: {str(e)}") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.post("/", response_model=Dict[str, Any]) async def create_worldbook( @@ -100,47 +47,8 @@ async def create_worldbook( ): """ 创建新世界书 - - Args: - name: 世界书名称 - description: 世界书描述 - file: 可选的上传文件(SillyTavern 格式) - - Returns: - Dict[str, Any]: 创建的世界书数据 """ - try: - # 如果上传了文件,从文件导入 - if file: - # 保存临时文件 - temp_path = os.path.join(settings.WORLDBOOKS_PATH, f"temp_{file.filename}") - with open(temp_path, "wb") as buffer: - shutil.copyfileobj(file.file, buffer) - - try: - # 从文件加载世界书 - world_book = WorldBook.load(Path(temp_path).stem) - # 更新名称和描述 - world_book.name = name - # 保存世界书 - world_book.save() - logger.info(f"从文件创建世界书: {name}") - finally: - # 删除临时文件 - if os.path.exists(temp_path): - os.remove(temp_path) - else: - # 创建空世界书 - world_book = WorldBook.create_empty(name) - logger.info(f"创建空世界书: {name}") - - return world_book.to_dict() - except HTTPException: - raise - except Exception as e: - logger.error(f"创建世界书 {name} 失败: {str(e)}") - raise HTTPException(status_code=500, detail=f"创建世界书失败: {str(e)}") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.put("/{name}", response_model=Dict[str, Any]) async def update_worldbook( @@ -149,417 +57,61 @@ async def update_worldbook( ): """ 更新世界书 - - Args: - name: 世界书名称 - description: 世界书描述(可选) - file: 可选的上传文件(SillyTavern 格式) - - Returns: - Dict[str, Any]: 更新后的世界书数据 """ - try: - # 检查世界书是否存在 - if not WorldBook.exists(name): - raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在") - - # 加载世界书 - world_book = WorldBook.load(name) - - # 如果上传了文件,从文件导入并合并 - if file: - # 保存临时文件 - temp_path = os.path.join(settings.WORLDBOOKS_PATH, f"temp_{file.filename}") - with open(temp_path, "wb") as buffer: - shutil.copyfileobj(file.file, buffer) - - try: - # 从文件加载世界书 - imported_book = WorldBook.load(Path(temp_path).stem) - # 合并条目 - world_book.merge_from_book(imported_book) - logger.info(f"从文件更新世界书: {name}") - finally: - # 删除临时文件 - if os.path.exists(temp_path): - os.remove(temp_path) - - # 保存世界书 - world_book.save() - logger.info(f"更新世界书: {name}") - - return world_book.to_dict() - except HTTPException: - raise - except Exception as e: - logger.error(f"更新世界书 {name} 失败: {str(e)}") - raise HTTPException(status_code=500, detail=f"更新世界书失败: {str(e)}") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.delete("/{name}") async def delete_worldbook(name: str): """ 删除世界书 - - Args: - name: 世界书名称 - - Returns: - Dict[str, Any]: 删除结果 """ - try: - # 检查世界书是否存在 - if not WorldBook.exists(name): - raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在") - - # 获取文件路径 - file_path = WorldBook.get_file_path(name) - - # 删除文件 - os.remove(file_path) - - logger.info(f"删除世界书: {name}") - return {"success": True, "message": f"世界书 '{name}' 已删除"} - except HTTPException: - raise - except Exception as e: - logger.error(f"删除世界书 {name} 失败: {str(e)}") - raise HTTPException(status_code=500, detail=f"删除世界书失败: {str(e)}") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.get("/{name}/entries", response_model=List[Dict[str, Any]]) async def list_worldbook_entries(name: str): """ - 获取世界书的所有条目(包括已禁用的条目) - - Args: - name: 世界书名称 - - Returns: - List[Dict[str, Any]]: 条目列表 + 获取世界书的所有条目 """ - try: - # 检查世界书是否存在 - if not WorldBook.exists(name): - raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在") - - # 加载世界书 - world_book = WorldBook.load(name) - - # 获取所有条目的核心信息 - entries = world_book.get_all_entries() - - logger.info(f"获取世界书 {name} 的所有条目: 共 {len(entries)} 个") - return entries - except HTTPException: - raise - except Exception as e: - logger.error(f"获取世界书 {name} 的条目失败: {str(e)}") - raise HTTPException(status_code=500, detail=f"获取世界书条目失败: {str(e)}") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.get("/{name}/entries/{uid}", response_model=Dict[str, Any]) async def get_worldbook_entry(name: str, uid: int): """ 获取世界书的指定条目 - - Args: - name: 世界书名称 - uid: 条目 UID - - Returns: - Dict[str, Any]: 条目数据 """ - try: - # 检查世界书是否存在 - if not WorldBook.exists(name): - raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在") - - # 加载世界书 - world_book = WorldBook.load(name) - - # 获取条目 - entry = world_book.get_entry(uid) - if entry is None: - raise HTTPException(status_code=404, detail=f"条目 UID {uid} 不存在") - - logger.info(f"获取世界书 {name} 的条目: UID={uid}") - return entry.to_dict() - except HTTPException: - raise - except Exception as e: - logger.error(f"获取世界书 {name} 的条目 {uid} 失败: {str(e)}") - raise HTTPException(status_code=500, detail=f"获取世界书条目失败: {str(e)}") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.post("/{name}/entries", response_model=Dict[str, Any]) async def create_worldbook_entry(name: str, entry_data: Dict[str, Any]): """ 在世界书中创建新条目 - - Args: - name: 世界书名称 - entry_data: 条目数据 - - Returns: - Dict[str, Any]: 创建的条目数据 """ - try: - # 检查世界书是否存在 - if not WorldBook.exists(name): - raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在") - - # 加载世界书 - world_book = WorldBook.load(name) - - # 处理触发配置数据 - trigger_data = entry_data.pop("trigger_config", None) - if trigger_data and "triggers" in trigger_data: - # 创建新的触发配置对象 - trigger_config = TriggerConfig() - - # 处理每个触发策略 - for strategy_str, trigger_info in trigger_data["triggers"].items(): - try: - strategy = TriggerStrategy(strategy_str) - enabled = trigger_info[0] if isinstance(trigger_info, list) and len(trigger_info) > 0 else False - config_data = trigger_info[1] if isinstance(trigger_info, list) and len(trigger_info) > 1 else None - - # 根据触发策略创建对应的配置对象 - if strategy == TriggerStrategy.KEYWORD and config_data: - config = KeywordTriggerConfig(**config_data) - elif strategy == TriggerStrategy.RAG and config_data: - config = RAGTriggerConfig(**config_data) - elif strategy == TriggerStrategy.CONDITION and config_data: - config = ConditionTriggerConfig(**config_data) - else: - config = None - - # 设置触发策略 - trigger_config.set_trigger(strategy, enabled, config) - except Exception as e: - logger.warning(f"处理触发策略 {strategy_str} 失败: {str(e)}") - continue - - # 设置触发配置 - entry_data["trigger_config"] = trigger_config - - # 创建条目 - entry = WorldItem.Entry(**entry_data) - - # 添加条目 - world_book.add_entry(entry) - - # 保存世界书 - world_book.save() - - logger.info(f"在世界书 {name} 中创建条目: UID={entry.uid}") - return entry.to_dict() - except HTTPException: - raise - except Exception as e: - logger.error(f"在世界书 {name} 中创建条目失败: {str(e)}") - raise HTTPException(status_code=500, detail=f"创建世界书条目失败: {str(e)}") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.put("/{name}/entries/{uid}", response_model=Dict[str, Any]) async def update_worldbook_entry(name: str, uid: int, entry_data: Dict[str, Any]): """ 更新世界书的指定条目 - - Args: - name: 世界书名称 - uid: 条目 UID - entry_data: 条目数据 - - Returns: - Dict[str, Any]: 更新后的条目数据 """ - try: - # 检查世界书是否存在 - if not WorldBook.exists(name): - raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在") - - # 加载世界书 - world_book = WorldBook.load(name) - - # 检查条目是否存在 - if world_book.get_entry(uid) is None: - raise HTTPException(status_code=404, detail=f"条目 UID {uid} 不存在") - - # 处理触发配置数据 - trigger_data = entry_data.pop("trigger_config", None) - if trigger_data and "triggers" in trigger_data: - # 创建新的触发配置对象 - trigger_config = TriggerConfig() - - # 处理每个触发策略 - for strategy_str, trigger_info in trigger_data["triggers"].items(): - try: - strategy = TriggerStrategy(strategy_str) - enabled = trigger_info[0] if isinstance(trigger_info, list) and len(trigger_info) > 0 else False - config_data = trigger_info[1] if isinstance(trigger_info, list) and len(trigger_info) > 1 else None - - # 根据触发策略创建对应的配置对象 - if strategy == TriggerStrategy.KEYWORD and config_data: - config = KeywordTriggerConfig(**config_data) - elif strategy == TriggerStrategy.RAG and config_data: - config = RAGTriggerConfig(**config_data) - elif strategy == TriggerStrategy.CONDITION and config_data: - config = ConditionTriggerConfig(**config_data) - else: - config = None - - # 设置触发策略 - trigger_config.set_trigger(strategy, enabled, config) - except Exception as e: - logger.warning(f"处理触发策略 {strategy_str} 失败: {str(e)}") - continue - - # 设置触发配置 - entry_data["trigger_config"] = trigger_config - - valid_fields = WorldItem.Entry.model_fields.keys() - filtered_data = {k: v for k, v in entry_data.items() if k in valid_fields} - - # 更新条目 - success = world_book.update_entry(uid, **filtered_data) - if not success: - raise HTTPException(status_code=500, detail="更新条目失败") - - # 保存世界书 - world_book.save() - - # 获取更新后的条目 - entry = world_book.get_entry(uid) - - logger.info(f"更新世界书 {name} 的条目: UID={uid}") - return entry.to_dict() - except HTTPException: - raise - except Exception as e: - logger.error(f"更新世界书 {name} 的条目 {uid} 失败: {str(e)}") - raise HTTPException(status_code=500, detail=f"更新世界书条目失败: {str(e)}") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.delete("/{name}/entries/{uid}") async def delete_worldbook_entry(name: str, uid: int): """ 删除世界书的指定条目 - - Args: - name: 世界书名称 - uid: 条目 UID - - Returns: - Dict[str, Any]: 删除结果 """ - try: - # 检查世界书是否存在 - if not WorldBook.exists(name): - raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在") - - # 加载世界书 - world_book = WorldBook.load(name) - - # 删除条目 - success = world_book.remove_entry(uid) - if not success: - raise HTTPException(status_code=404, detail=f"条目 UID {uid} 不存在") - - # 保存世界书 - world_book.save() - - logger.info(f"删除世界书 {name} 的条目: UID={uid}") - return {"success": True, "message": f"条目 UID {uid} 已删除"} - except HTTPException: - raise - except Exception as e: - logger.error(f"删除世界书 {name} 的条目 {uid} 失败: {str(e)}") - raise HTTPException(status_code=500, detail=f"删除世界书条目失败: {str(e)}") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.post("/{name}/import", response_model=Dict[str, Any]) async def import_worldbook(name: str, file: UploadFile = File(...)): """ 从文件导入世界书 - - Args: - name: 世界书名称 - file: 上传的文件(SillyTavern 格式) - - Returns: - Dict[str, Any]: 导入的世界书数据 """ - try: - # 保存临时文件 - temp_path = os.path.join(settings.WORLDBOOKS_PATH, f"temp_{file.filename}") - with open(temp_path, "wb") as buffer: - shutil.copyfileobj(file.file, buffer) - - try: - # 从文件加载世界书 - world_book = WorldBook.load(Path(temp_path).stem) - - # 如果世界书已存在,合并条目 - if WorldBook.exists(name): - existing_book = WorldBook.load(name) - existing_book.merge_from_book(world_book) - # 保存合并后的世界书 - existing_book.save() - world_book = existing_book - logger.info(f"导入并合并世界书: {name}") - else: - # 设置名称并保存 - world_book.name = name - world_book.save() - logger.info(f"导入新世界书: {name}") - - return world_book.to_dict() - finally: - # 删除临时文件 - if os.path.exists(temp_path): - os.remove(temp_path) - except Exception as e: - logger.error(f"导入世界书 {name} 失败: {str(e)}") - raise HTTPException(status_code=500, detail=f"导入世界书失败: {str(e)}") - + raise HTTPException(status_code=501, detail="Not Implemented") @router.get("/{name}/export") async def export_worldbook(name: str): """ 导出世界书为 SillyTavern 格式 - - Args: - name: 世界书名称 - - Returns: - FileResponse: 导出的文件 """ - try: - # 检查世界书是否存在 - if not WorldBook.exists(name): - raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在") - - # 加载世界书 - world_book = WorldBook.load(name) - - # 创建导出文件路径 - export_path = os.path.join(settings.WORLDBOOKS_PATH, f"export_{name}.json") - - # 导出为 SillyTavern 格式 - world_book.to_sillytavern_json(export_path) - - logger.info(f"导出世界书: {name}") - - # 返回文件 - return FileResponse( - path=export_path, - filename=f"{name}.json", - media_type="application/json" - ) - except HTTPException: - raise - except Exception as e: - logger.error(f"导出世界书 {name} 失败: {str(e)}") - raise HTTPException(status_code=500, detail=f"导出世界书失败: {str(e)}") + raise HTTPException(status_code=501, detail="Not Implemented") diff --git a/backend/core/items.py b/backend/core/items.py deleted file mode 100644 index fbdb3b0..0000000 --- a/backend/core/items.py +++ /dev/null @@ -1,24 +0,0 @@ -from pydantic import BaseModel, Field -from typing import Optional, List - - -# 1. 定义请求体模型 -class ChatRequest(BaseModel): - # --- 基础信息 --- - mes: str = Field(..., description="用户输入的消息内容") - is_user: bool = Field(..., description="标识发送者是否为用户(True为用户,False为AI)") - floor_number: int = Field(..., description="当前对话的楼层号,用于判断是否为重试(Regenerate)请求") - - # --- 身份与会话 --- - name: str = Field("default", description="发送者的显示名称,默认为'default'") - role_name: Optional[str] = Field(None, description="当前绑定的角色名称") - chat_name: Optional[str] = Field(None, description="当前会话的标识名称") - preset: Optional[str] = Field(None, description="预设的提示词或系统指令") - - # --- 功能开关 --- - stream: bool = Field(False, description="是否开启流式输出") - img_switch: bool = Field(False, description="是否开启图片生成功能") - table_switch: bool = Field(False, description="是否开启表格生成功能") - -# 其他可能需要的参数,比如历史记录,可以在这里加 - # history: Optional[List[Dict]] = None diff --git a/backend/core/models/PromptComponent.py b/backend/core/models/PromptComponent.py deleted file mode 100644 index 04208ee..0000000 --- a/backend/core/models/PromptComponent.py +++ /dev/null @@ -1,65 +0,0 @@ -from pydantic import BaseModel, Field, validator -from typing import Dict, Any - - -class PromptComponent(BaseModel): - """预设组件类,代表一个独立的提示词模块""" - - identifier: str = Field(..., description="唯一标识符,用于引用和定位组件") - name: str = Field(..., description="组件显示名称") - content: str = Field("", description="组件内容文本") - # 0:System,1:User,2:Assistant - role: int = Field(0, description="角色身份(0:System,1:User,2:Assistant)") - system_prompt: bool = Field(False, description="是否强制作为系统提示词处理") - marker: bool = Field(False, description="是否为动态插入点占位符") - - @validator('role') - def validate_role(cls, v): - """验证角色值是否在有效范围内""" - if not isinstance(v, int) or v not in [0, 1, 2]: - raise ValueError("角色值必须是0(System)、1(User)或2(Assistant)") - return v - - def update(self, **kwargs) -> None: - """ - 更新组件属性 - - 参数: - **kwargs: 要更新的字段和值 - - 异常: - ValueError: 当尝试更新identifier时抛出 - """ - if 'identifier' in kwargs: - raise ValueError("组件标识符不可修改") - - for key, value in kwargs.items(): - if hasattr(self, key): - setattr(self, key, value) - - def to_dict(self) -> Dict[str, Any]: - """ - 将组件转换为字典 - - 返回: - Dict[str, Any]: 组件的字典表示 - """ - return self.dict() - - @classmethod - def from_dict(cls, data: Dict[str, Any]) -> 'PromptComponent': - """ - 从字典创建组件实例,自动处理role字段的类型转换 - - 参数: - data: 包含组件数据的字典 - - 返回: - PromptComponent: 组件实例 - """ - # 处理role字段,将字符串转换为整数 - if 'role' in data and isinstance(data['role'], str): - role_map = {'system': 0, 'user': 1, 'assistant': 2} - data['role'] = role_map.get(data['role'].lower(), 0) - - return cls(**data) diff --git a/backend/core/models/PromptList.py b/backend/core/models/PromptList.py deleted file mode 100644 index e832179..0000000 --- a/backend/core/models/PromptList.py +++ /dev/null @@ -1,591 +0,0 @@ -from pydantic import BaseModel, Field, validator -from typing import List, Dict, Any, Optional -from pathlib import Path -import json -from .PromptComponent import PromptComponent - - -class AIDesignSpec(BaseModel): - """AI设计规范类,包含模型生成的核心参数和动态结构配置""" - - # [Base] 基础核心参数 - temperature: float = Field(1.0, description="生成温度,控制随机性(0-2)") - frequency_penalty: float = Field(0.0, description="频率惩罚,降低重复token概率") - presence_penalty: float = Field(0.0, description="存在惩罚,鼓励谈论新话题") - top_p: float = Field(1.0, description="核采样,控制词汇选择范围") - top_k: int = Field(0, description="随机采样范围,从概率最高的K个词中选择") - top_a: float = Field(0.0, description="基于平方概率分布的采样") - min_p: float = Field(0.0, description="最小概率阈值") - repetition_penalty: float = Field(1.0, description="重复惩罚系数(1.0-1.2)") - max_context: int = Field(2048, description="上下文窗口大小(Token上限)") - max_tokens: int = Field(250, description="单次回复的最大长度") - max_context_unlocked: bool = Field(False, description="是否允许超出限制的上下文") - names_behavior: int = Field(0, description="名字处理行为(0=默认,1=始终包含,2=仅角色)") - send_if_empty: str = Field("", description="用户发送空消息时自动填充的内容") - impersonation_prompt: str = Field("", description="模仿模式下使用的提示词") - new_chat_prompt: str = Field("", description="开启新聊天时自动发送的系统提示") - new_group_chat_prompt: str = Field("", description="开启新群组聊天时的提示") - new_example_chat_prompt: str = Field("", description="新示例聊天的提示") - continue_nudge_prompt: str = Field("", description="续写功能触发的提示词") - bias_preset_selected: str = Field("", description="选用的偏见预设") - wi_format: str = Field("{0}", description="世界书条目的格式化字符串") - scenario_format: str = Field("{{scenario}}", description="场景描述的格式化字符串") - personality_format: str = Field("", description="角色性格的格式化字符串") - group_nudge_prompt: str = Field("", description="群组聊天中提示AI仅以特定角色回复的提示词") - stream: bool = Field(True, description="是否使用流式输出") - assistant_prefill: str = Field("", description="强制AI回复的开头内容") - assistant_impersonation: str = Field("", description="模仿模式下强制AI回复的开头内容") - use_sysprompt: bool = Field(True, description="是否强制将提示词注入系统层") - squash_system_messages: bool = Field(False, description="是否压缩系统消息") - media_inlining: bool = Field(False, description="是否内联媒体描述") - continue_prefill: bool = Field(True, description="续写时是否预填充内容") - continue_postfix: str = Field(" ", description="续写时添加的后缀") - seed: int = Field(-1, description="随机种子(-1为随机)") - n: int = Field(1, description="生成回复的数量") - - # [Dynamic] 动态结构 - prompts: List[PromptComponent] = Field( - default_factory=list, - description="组件库,定义所有可用的积木块" - ) - prompt_order: List[str] = Field( - default_factory=list, - description="组装说明书,定义构建最终提示词的顺序" - ) - - @validator('prompts') - def validate_prompts_unique_identifier(cls, v): - """验证组件标识符唯一性""" - identifiers = [comp.identifier for comp in v] - if len(identifiers) != len(set(identifiers)): - raise ValueError("组件标识符必须唯一") - return v - - @validator('prompt_order') - def validate_prompt_order_exists(cls, v, values): - """验证prompt_order中的组件ID是否存在于prompts中""" - if 'prompts' in values: - prompt_ids = {comp.identifier for comp in values['prompts']} - invalid_ids = set(v) - prompt_ids - if invalid_ids: - raise ValueError(f"prompt_order中包含不存在的组件ID: {invalid_ids}") - return v - - @classmethod - def get_preset_dir(cls) -> Path: - """获取预设目录路径""" - try: - from backend.core.config import settings - preset_dir = settings.DATA_PATH / "preset" - # 如果路径不存在,尝试使用相对路径 - if not preset_dir.exists(): - # 尝试从当前工作目录构建路径 - cwd_preset_dir = Path.cwd() / "data" / "preset" - if cwd_preset_dir.exists(): - return cwd_preset_dir - # 尝试从脚本所在目录构建路径 - script_dir = Path(__file__).resolve().parent.parent.parent - script_preset_dir = script_dir / "data" / "preset" - if script_preset_dir.exists(): - return script_preset_dir - # 如果都不存在,返回默认路径 - return Path("data/preset") - return preset_dir - except ImportError: - # 如果无法导入settings,尝试使用相对路径 - cwd_preset_dir = Path.cwd() / "data" / "preset" - if cwd_preset_dir.exists(): - return cwd_preset_dir - # 尝试从脚本所在目录构建路径 - script_dir = Path(__file__).resolve().parent.parent.parent - script_preset_dir = script_dir / "data" / "preset" - if script_preset_dir.exists(): - return script_preset_dir - # 如果都不存在,返回默认路径 - return Path("data/preset") - - @classmethod - async def list_all_presets(cls) -> Dict[str, List[Dict]]: - """获取所有预设列表及其基本信息""" - preset_dir = cls.get_preset_dir() - if not preset_dir.exists(): - return {"presets": []} - - presets = [] - for preset_file in preset_dir.glob("*.json"): - try: - with open(preset_file, 'r', encoding='utf-8') as f: - preset_data = json.load(f) - presets.append({ - "name": preset_file.stem, - "description": preset_data.get("description", ""), - "component_count": len(preset_data.get("prompts", [])), - "temperature": preset_data.get("temperature", 1.0) - }) - except Exception: - continue # 跳过损坏的预设文件 - return {"presets": presets} - - @classmethod - async def get_preset(cls, preset_name: str) -> Dict[str, Any]: - """获取指定预设的完整内容""" - preset_path = cls.get_preset_dir() / f"{preset_name}.json" - if not preset_path.exists(): - raise FileNotFoundError(f"Preset not found: {preset_name}") - - try: - with open(preset_path, 'r', encoding='utf-8') as f: - preset_data = json.load(f) - - # 处理prompt_order,简化为单角色配置 - if 'prompt_order' in preset_data and isinstance(preset_data['prompt_order'], list) and len( - preset_data['prompt_order']) > 0: - # 检查第一个元素是否为字典(多角色配置) - first_item = preset_data['prompt_order'][0] - if isinstance(first_item, dict) and 'order' in first_item: - # 提取第一个角色的order配置 - first_role_order = first_item - if isinstance(first_role_order['order'], list): - # 简化为只包含enabled为True的identifier列表 - simplified_order = [ - item.get('identifier') - for item in first_role_order['order'] - if item.get('enabled', True) - ] - preset_data['prompt_order'] = simplified_order - - # 转换为AIDesignSpec对象进行验证 - ai_design_spec = cls.from_dict(preset_data) - - # 构建返回数据,确保格式与前端期望的一致 - result = { - # 基础参数 - "temperature": ai_design_spec.temperature, - "frequency_penalty": ai_design_spec.frequency_penalty, - "presence_penalty": ai_design_spec.presence_penalty, - "top_p": ai_design_spec.top_p, - "top_k": ai_design_spec.top_k, - "max_context": ai_design_spec.max_context, - "max_tokens": ai_design_spec.max_tokens, - "max_context_unlocked": ai_design_spec.max_context_unlocked, - "stream_openai": ai_design_spec.stream, - "seed": ai_design_spec.seed, - "n": ai_design_spec.n, - - # 兼容旧格式 - "openai_max_context": ai_design_spec.max_context, - "openai_max_tokens": ai_design_spec.max_tokens, - - # 其他参数 - "top_a": ai_design_spec.top_a, - "min_p": ai_design_spec.min_p, - "repetition_penalty": ai_design_spec.repetition_penalty, - "names_behavior": ai_design_spec.names_behavior, - "send_if_empty": ai_design_spec.send_if_empty, - "impersonation_prompt": ai_design_spec.impersonation_prompt, - "new_chat_prompt": ai_design_spec.new_chat_prompt, - "new_group_chat_prompt": ai_design_spec.new_group_chat_prompt, - "new_example_chat_prompt": ai_design_spec.new_example_chat_prompt, - "continue_nudge_prompt": ai_design_spec.continue_nudge_prompt, - "bias_preset_selected": ai_design_spec.bias_preset_selected, - "wi_format": ai_design_spec.wi_format, - "scenario_format": ai_design_spec.scenario_format, - "personality_format": ai_design_spec.personality_format, - "group_nudge_prompt": ai_design_spec.group_nudge_prompt, - "assistant_prefill": ai_design_spec.assistant_prefill, - "assistant_impersonation": ai_design_spec.assistant_impersonation, - "use_sysprompt": ai_design_spec.use_sysprompt, - "squash_system_messages": ai_design_spec.squash_system_messages, - "media_inlining": ai_design_spec.media_inlining, - "continue_prefill": ai_design_spec.continue_prefill, - "continue_postfix": ai_design_spec.continue_postfix, - - # 处理组件 - "prompts": [] - } - - # 处理组件列表 - if ai_design_spec.prompts: - # 获取当前角色的prompt_order(简化后的字符串列表) - current_order = ai_design_spec.prompt_order if ai_design_spec.prompt_order else [] - - # 构建组件列表 - for prompt in ai_design_spec.prompts: - # 检查组件是否在order中 - is_in_order = prompt.identifier in current_order - - # 构建组件对象 - component = { - "identifier": prompt.identifier, - "name": prompt.name, - "content": prompt.content if hasattr(prompt, 'content') else "", - "role": prompt.role if hasattr(prompt, 'role') else (0 if prompt.system_prompt else 1), - "system_prompt": prompt.system_prompt, - "marker": prompt.marker, - "enabled": is_in_order if current_order else True - } - - result["prompts"].append(component) - - # 按照order排序组件 - if current_order: - result["prompts"].sort( - key=lambda x: current_order.index(x["identifier"]) if x[ - "identifier"] in current_order else len( - current_order)) - - # 添加prompt_order - result["prompt_order"] = ai_design_spec.prompt_order if ai_design_spec.prompt_order else [] - - return result - except Exception as e: - raise Exception(f"Failed to load preset: {str(e)}") - - @classmethod - async def create_preset(cls, preset_name: str, preset_data: Dict) -> Dict[str, str]: - """创建新预设""" - preset_dir = cls.get_preset_dir() - preset_dir.mkdir(parents=True, exist_ok=True) - preset_path = preset_dir / f"{preset_name}.json" - - if preset_path.exists(): - raise FileExistsError(f"Preset already exists: {preset_name}") - - try: - # 验证并转换为AIDesignSpec对象 - ai_design_spec = cls.from_dict(preset_data) - - # 保存到文件 - with open(preset_path, 'w', encoding='utf-8') as f: - json.dump(ai_design_spec.dict(), f, ensure_ascii=False, indent=2) - - return {"message": "Preset created successfully", "name": preset_name} - except Exception as e: - raise Exception(f"Failed to create preset: {str(e)}") - - @classmethod - async def update_preset(cls, preset_name: str, update_data: Dict) -> Dict[str, str]: - """更新预设配置""" - preset_path = cls.get_preset_dir() / f"{preset_name}.json" - if not preset_path.exists(): - raise FileNotFoundError(f"Preset not found: {preset_name}") - - try: - # 加载现有预设 - with open(preset_path, 'r', encoding='utf-8') as f: - preset_data = json.load(f) - - # 更新字段 - for key, value in update_data.items(): - preset_data[key] = value - - # 验证并转换为AIDesignSpec对象 - ai_design_spec = cls.from_dict(preset_data) - - # 保存更新 - with open(preset_path, 'w', encoding='utf-8') as f: - json.dump(ai_design_spec.dict(), f, ensure_ascii=False, indent=2) - - return {"message": "Preset updated successfully"} - except Exception as e: - raise Exception(f"Failed to update preset: {str(e)}") - - @classmethod - async def delete_preset(cls, preset_name: str) -> Dict[str, str]: - """删除指定预设""" - preset_path = cls.get_preset_dir() / f"{preset_name}.json" - if not preset_path.exists(): - raise FileNotFoundError(f"Preset not found: {preset_name}") - - try: - preset_path.unlink() - return {"message": "Preset deleted successfully"} - except Exception as e: - raise Exception(f"Failed to delete preset: {str(e)}") - - @classmethod - async def list_components(cls, preset_name: str) -> Dict[str, List[Dict]]: - """获取预设中的所有组件""" - preset_path = cls.get_preset_dir() / f"{preset_name}.json" - if not preset_path.exists(): - raise FileNotFoundError(f"Preset not found: {preset_name}") - - try: - with open(preset_path, 'r', encoding='utf-8') as f: - preset_data = json.load(f) - - # 获取组件列表 - components = preset_data.get("prompts", []) - return {"components": components} - except Exception as e: - raise Exception(f"Failed to load components: {str(e)}") - - @classmethod - async def get_component(cls, preset_name: str, component_id: str) -> Dict[str, Any]: - """获取指定组件的详情""" - preset_path = cls.get_preset_dir() / f"{preset_name}.json" - if not preset_path.exists(): - raise FileNotFoundError(f"Preset not found: {preset_name}") - - try: - with open(preset_path, 'r', encoding='utf-8') as f: - preset_data = json.load(f) - - # 查找组件 - components = preset_data.get("prompts", []) - component = next((c for c in components if c.get("identifier") == component_id), None) - - if not component: - raise FileNotFoundError(f"Component not found: {component_id}") - - return component - except FileNotFoundError: - raise - except Exception as e: - raise Exception(f"Failed to load component: {str(e)}") - - @classmethod - async def add_component_to_preset(cls, preset_name: str, component_data: Dict) -> Dict[str, str]: - """向预设添加新组件""" - preset_path = cls.get_preset_dir() / f"{preset_name}.json" - if not preset_path.exists(): - raise FileNotFoundError(f"Preset not found: {preset_name}") - - try: - # 加载预设数据 - with open(preset_path, 'r', encoding='utf-8') as f: - preset_data = json.load(f) - - # 验证组件数据 - component = PromptComponent(**component_data) - - # 检查组件ID是否已存在 - components = preset_data.get("prompts", []) - if any(c.get("identifier") == component.identifier for c in components): - raise ValueError(f"Component identifier already exists: {component.identifier}") - - # 添加组件 - components.append(component.dict()) - preset_data["prompts"] = components - - # 保存更新 - with open(preset_path, 'w', encoding='utf-8') as f: - json.dump(preset_data, f, ensure_ascii=False, indent=2) - - return {"message": "Component added successfully", "identifier": component.identifier} - except (FileNotFoundError, ValueError): - raise - except Exception as e: - raise Exception(f"Failed to add component: {str(e)}") - - @classmethod - async def update_component_in_preset(cls, preset_name: str, component_id: str, update_data: Dict) -> Dict[str, str]: - """更新指定组件""" - preset_path = cls.get_preset_dir() / f"{preset_name}.json" - if not preset_path.exists(): - raise FileNotFoundError(f"Preset not found: {preset_name}") - - try: - # 加载预设数据 - with open(preset_path, 'r', encoding='utf-8') as f: - preset_data = json.load(f) - - # 查找并更新组件 - components = preset_data.get("prompts", []) - component_index = next((i for i, c in enumerate(components) if c.get("identifier") == component_id), None) - - if component_index is None: - raise FileNotFoundError(f"Component not found: {component_id}") - - # 更新组件字段 - for key, value in update_data.items(): - components[component_index][key] = value - - # 保存更新 - with open(preset_path, 'w', encoding='utf-8') as f: - json.dump(preset_data, f, ensure_ascii=False, indent=2) - - return {"message": "Component updated successfully"} - except FileNotFoundError: - raise - except Exception as e: - raise Exception(f"Failed to update component: {str(e)}") - - @classmethod - async def delete_component_from_preset(cls, preset_name: str, component_id: str) -> Dict[str, str]: - """从预设中删除指定组件""" - preset_path = cls.get_preset_dir() / f"{preset_name}.json" - if not preset_path.exists(): - raise FileNotFoundError(f"Preset not found: {preset_name}") - - try: - # 加载预设数据 - with open(preset_path, 'r', encoding='utf-8') as f: - preset_data = json.load(f) - - # 查找并删除组件 - components = preset_data.get("prompts", []) - original_length = len(components) - components = [c for c in components if c.get("identifier") != component_id] - - if len(components) == original_length: - raise FileNotFoundError(f"Component not found: {component_id}") - - # 更新预设数据 - preset_data["prompts"] = components - - # 保存更新 - with open(preset_path, 'w', encoding='utf-8') as f: - json.dump(preset_data, f, ensure_ascii=False, indent=2) - - return {"message": "Component deleted successfully"} - except FileNotFoundError: - raise - except Exception as e: - raise Exception(f"Failed to delete component: {str(e)}") - - # ========== 组件管理方法 ========== - - def add_component(self, component: PromptComponent) -> None: - """ - 添加新组件 - - 参数: - component: 要添加的组件 - - 异常: - ValueError: 当组件标识符已存在时抛出 - """ - if any(c.identifier == component.identifier for c in self.prompts): - raise ValueError(f"组件标识符 {component.identifier} 已存在") - self.prompts.append(component) - - def remove_component(self, identifier: str) -> bool: - """ - 移除指定组件 - - 参数: - identifier: 组件标识符 - - 返回: - bool: 是否成功移除 - """ - original_length = len(self.prompts) - self.prompts = [c for c in self.prompts if c.identifier != identifier] - - # 同时从prompt_order中移除 - self.prompt_order = [id for id in self.prompt_order if id != identifier] - - return len(self.prompts) < original_length - - def get_component(self, identifier: str) -> Optional[PromptComponent]: - """ - 获取指定组件 - - 参数: - identifier: 组件标识符 - - 返回: - Optional[PromptComponent]: 找到的组件,未找到返回None - """ - for component in self.prompts: - if component.identifier == identifier: - return component - return None - - def update_component(self, identifier: str, **kwargs) -> bool: - """ - 更新指定组件 - - 参数: - identifier: 组件标识符 - **kwargs: 要更新的字段 - - 返回: - bool: 是否成功更新 - """ - component = self.get_component(identifier) - if component is None: - return False - - component.update(**kwargs) - return True - - def list_components(self) -> List[Dict[str, Any]]: - """ - 列出所有组件 - - 返回: - List[Dict[str, Any]]: 组件字典列表 - """ - return [component.to_dict() for component in self.prompts] - - def reorder_components(self, new_order: List[str]) -> None: - """ - 重新排序组件 - - 参数: - new_order: 新的组件标识符顺序 - - 异常: - ValueError: 当包含不存在的组件ID时抛出 - """ - # 验证所有ID都存在 - existing_ids = {c.identifier for c in self.prompts} - invalid_ids = set(new_order) - existing_ids - - if invalid_ids: - raise ValueError(f"包含不存在的组件ID: {invalid_ids}") - - self.prompt_order = new_order - - def get_ordered_components(self) -> List[PromptComponent]: - """ - 获取按prompt_order排序的组件列表 - - 返回: - List[PromptComponent]: 排序后的组件列表 - """ - component_map = {c.identifier: c for c in self.prompts} - ordered_components = [] - - for identifier in self.prompt_order: - if identifier in component_map: - ordered_components.append(component_map[identifier]) - - # 添加未在prompt_order中的组件 - ordered_components.extend([ - c for c in self.prompts - if c.identifier not in self.prompt_order - ]) - - return ordered_components - - def to_dict(self) -> Dict[str, Any]: - """ - 将设计规范转换为字典 - - 返回: - Dict[str, Any]: 设计规范的字典表示 - """ - return self.dict() - - @classmethod - def from_dict(cls, data: Dict[str, Any]) -> 'AIDesignSpec': - """ - 从字典创建设计规范实例 - - 参数: - data: 包含设计规范数据的字典 - - 返回: - AIDesignSpec: 设计规范实例 - """ - # 处理prompts字段 - if 'prompts' in data: - data['prompts'] = [ - PromptComponent.from_dict(comp) if isinstance(comp, dict) else comp - for comp in data['prompts'] - ] - - return cls(**data) diff --git a/backend/core/models/WorldBook.py b/backend/core/models/WorldBook.py deleted file mode 100644 index 0066239..0000000 --- a/backend/core/models/WorldBook.py +++ /dev/null @@ -1,438 +0,0 @@ -import json -import os -import logging -from typing import Dict, List, Optional, Any -from pathlib import Path -from pydantic import BaseModel, Field, field_validator -from .WorldItem import WorldItem, TriggerStrategy -from backend.core.config import settings - -# 配置日志 -logger = logging.getLogger(__name__) - - -class WorldBook(BaseModel): - """ - 世界书集合模型 - 管理多个世界书条目,支持导入导出 SillyTavern 格式 - """ - # 世界书基本信息 - name: str = Field(..., description="世界书名称") - - # 条目集合 - entries: Dict[str, WorldItem.Entry] = Field( - default_factory=dict, - description="世界书条目字典对象 (Key-Value Map)" - ) - - @field_validator('entries') - @classmethod - def validate_entries_unique_uid(cls, v): - """验证条目 UID 的唯一性""" - uids = [entry.uid for entry in v.values()] - if len(uids) != len(set(uids)): - logger.error("验证失败: 条目 UID 必须唯一") - raise ValueError("条目 UID 必须唯一") - return v - - @classmethod - def get_file_path(cls, name: str) -> str: - """ - 根据世界书名称获取文件路径 - - Args: - name: 世界书名称 - - Returns: - str: 完整的文件路径 - """ - # 使用配置中的 WORLDBOOKS_PATH - return str(settings.WORLDBOOKS_PATH / f"{name}.json") - - @classmethod - def exists(cls, name: str) -> bool: - """ - 检查指定名称的世界书文件是否存在 - - Args: - name: 世界书名称 - - Returns: - bool: 文件是否存在 - """ - file_path = cls.get_file_path(name) - return os.path.exists(file_path) - - @classmethod - def create_empty(cls, name: str) -> 'WorldBook': - """ - 创建并保存一个空白的世界书 - - Args: - name: 世界书名称 - Returns: - WorldBook: 创建的世界书对象 - - Raises: - ValueError: 世界书已存在 - IOError: 文件写入失败 - """ - # 检查世界书是否已存在 - if cls.exists(name): - raise ValueError(f"世界书 '{name}' 已存在") - - # 创建空白世界书对象 - world_book = cls( - name=name, - ) - - # 保存世界书 - world_book.save() - - logger.info(f"创建空白世界书: {name}") - return world_book - - def add_entry(self, entry: WorldItem.Entry) -> None: - """ - 添加世界书条目 - - Args: - entry: 世界书条目对象 - - Raises: - ValueError: 条目 UID 已存在 - """ - entry_key = str(entry.uid) - if entry_key in self.entries: - error_msg = f"添加条目失败: 条目 UID {entry.uid} 已存在于世界书 {self.name}" - logger.error(error_msg) - raise ValueError(error_msg) - self.entries[entry_key] = entry - logger.debug(f"已添加条目: UID={entry.uid}, 世界书={self.name}") - - def remove_entry(self, uid: int) -> bool: - """ - 移除世界书条目 - - Args: - uid: 条目 UID - - Returns: - bool: 是否成功移除 - """ - entry_key = str(uid) - if entry_key in self.entries: - del self.entries[entry_key] - logger.info(f"已从世界书 {self.name} 移除条目: UID={uid}") - return True - logger.warning(f"尝试移除不存在的条目: 世界书 {self.name} 中未找到 UID={uid}") - return False - - def get_entry(self, uid: int) -> Optional[WorldItem.Entry]: - """ - 获取指定 UID 的世界书条目 - - Args: - uid: 条目 UID - - Returns: - Optional[WorldItem.Entry]: 找到的条目,未找到返回 None - """ - entry_key = str(uid) - entry = self.entries.get(entry_key) - if entry: - logger.debug(f"从世界书 {self.name} 获取条目: UID={uid}") - else: - logger.debug(f"在世界书 {self.name} 中未找到条目: UID={uid}") - return entry - - def update_entry(self, uid: int, **kwargs) -> bool: - """ - 更新世界书条目 - - Args: - uid: 条目 UID - **kwargs: 要更新的字段 - - Returns: - bool: 是否成功更新 - """ - entry = self.get_entry(uid) - if entry is None: - logger.warning(f"更新条目失败: 在世界书 {self.name} 中未找到 UID={uid}") - return False - - for key, value in kwargs.items(): - if hasattr(entry, key): - setattr(entry, key, value) - logger.info(f"已更新世界书 {self.name} 中的条目: UID={uid}, 更新字段={list(kwargs.keys())}") - return True - - def filter_by_position(self, position: int) -> List[WorldItem.Entry]: - """ - 根据位置筛选条目 - - Args: - position: 位置值 - - Returns: - List[WorldItem.Entry]: 筛选后的条目列表 - """ - filtered_entries = [ - entry for entry in self.entries.values() - if entry.position == position - ] - logger.debug( - f"在世界书 {self.name} 中按位置筛选: 值={position}, 结果数量={len(filtered_entries)}") - return filtered_entries - - def get_summary(self) -> Dict[str, Any]: - """ - 获取世界书概要信息 - - Returns: - Dict[str, Any]: 概要信息字典 - """ - summary = { - "name": self.name, - "entry_count": len(self.entries), - "trigger_strategies": { - strategy.value: sum(1 for e in self.entries.values() - if strategy in e.trigger_config.get_enabled_triggers()) - for strategy in TriggerStrategy - } - } - logger.debug(f"获取世界书 {self.name} 的概要信息") - return summary - - def to_summary_dict(self) -> Dict[str, Any]: - """ - 生成世界书摘要信息,用于列表显示 - - Returns: - Dict[str, Any]: 包含基本信息的字典 - """ - summary = { - "name": self.name, - "entry_count": len(self.entries), - } - logger.debug(f"生成世界书 {self.name} 的摘要信息") - return summary - - def to_dict(self) -> Dict: - """ - 将 WorldBook 转换为字典 - - Returns: - Dict: 世界书数据字典 - """ - result = { - 'name': self.name, - 'entries': {uid: entry.model_dump() for uid, entry in self.entries.items()} - } - logger.debug(f"将世界书 {self.name} 转换为字典") - return result - - @classmethod - def load(cls, name: str) -> 'WorldBook': - """ - 从文件加载世界书(只有 entries 字段的格式) - - Args: - name: 世界书名称 - - Returns: - WorldBook: 世界书对象 - - Raises: - FileNotFoundError: 文件不存在 - ValueError: 格式不符合标准 - json.JSONDecodeError: JSON 解析错误 - """ - file_path = cls.get_file_path(name) - if not os.path.exists(file_path): - error_msg = f"世界书文件未找到: {file_path}" - logger.error(error_msg) - raise FileNotFoundError(error_msg) - - try: - with open(file_path, 'r', encoding='utf-8') as f: - raw_data = json.load(f) - - # 世界书名称始终使用文件名(不包括后缀名) - world_name = name - - # 直接使用 entries 字段 - entries_dict = raw_data.get("entries", {}) - if not isinstance(entries_dict, dict): - error_msg = "无效的世界书格式:'entries' 字段必须是一个字典。" - logger.error(error_msg) - raise ValueError(error_msg) - - # 创建世界书对象 - world_book = cls( - name=world_name, - ) - - # 转换标准格式的条目 - for uid, entry_data in entries_dict.items(): - try: - # 先使用 WorldItem 解析数据 - world_item = WorldItem.from_sillytavern_data(entry_data) - # 然后转换为 Entry - world_entry = world_item.to_entry() - world_book.add_entry(world_entry) - except Exception as e: - logger.warning(f"跳过条目 {uid},解析失败: {e}") - - logger.info( - f"从文件加载世界书: 文件={file_path}, 名称={world_name}, 条目数={len(world_book.entries)}") - - return world_book - except json.JSONDecodeError as e: - error_msg = f"JSON 解析错误: {str(e)}" - logger.error(error_msg) - raise ValueError(error_msg) - except Exception as e: - error_msg = f"从文件加载世界书失败: {str(e)}" - logger.error(error_msg) - raise ValueError(error_msg) - - def save(self) -> None: - """ - 保存世界书到文件(只有 entries 字段的格式) - 如果文件不存在,会创建新文件;如果文件存在,会更新现有文件 - - Raises: - IOError: 文件写入失败 - """ - file_path = self.get_file_path(self.name) - - # 确保目录存在 - os.makedirs(Path(file_path).parent, exist_ok=True) - - try: - # 转换为标准格式 - entries_dict = {} - for uid, entry in self.entries.items(): - entries_dict[uid] = entry.to_sillytavern_dict() - - output_data = { - "entries": entries_dict - } - - with open(file_path, 'w', encoding='utf-8') as f: - json.dump(output_data, f, ensure_ascii=False, indent=2) - - logger.info( - f"世界书已保存: 文件={file_path}, 名称={self.name}, 条目数={len(self.entries)}") - except Exception as e: - error_msg = f"保存世界书失败: {str(e)}" - logger.error(error_msg) - raise IOError(error_msg) - - def list_triggers_and_content(self) -> List[Dict[str, Any]]: - """ - 提取所有条目的触发关键词和内容,用于快速构建向量数据库或索引 - - Returns: - List[Dict[str, Any]]: 包含 trigger (key) 和 content 的列表 - """ - result = [] - for entry in self.entries.values(): - entry_dict = entry.to_dict() - # 添加额外的触发相关信息 - enabled_triggers = entry.trigger_config.get_enabled_triggers() - keyword_enabled, keyword_config = entry.trigger_config.get_trigger(TriggerStrategy.KEYWORD) - constant_enabled, _ = entry.trigger_config.get_trigger(TriggerStrategy.CONSTANT) - - entry_dict.update({ - "triggers": keyword_config.key if keyword_enabled and keyword_config else [], - "constant": constant_enabled, - "trigger_strategies": [strategy.value for strategy in enabled_triggers] - }) - result.append(entry_dict) - - logger.debug(f"列出世界书 {self.name} 的触发词和内容: 条目数={len(result)}") - return result - - def get_all_entries(self) -> List[Dict[str, Any]]: - """ - 获取所有条目的核心信息(包括已禁用的条目) - - Returns: - List[Dict[str, Any]]: 包含核心信息的条目列表 - """ - result = [entry.to_dict() for entry in self.entries.values()] - logger.debug(f"获取世界书 {self.name} 的所有条目: 条目数={len(result)}") - return result - - def merge_from_book(self, other_book: 'WorldBook') -> None: - """ - 从另一个世界书合并条目 - - Args: - other_book: 要合并的世界书对象 - """ - for uid, entry in other_book.entries.items(): - if uid in self.entries: - # 更新现有条目 - for key, value in entry.dict().items(): - if key != 'uid': # 不更新 UID - setattr(self.entries[uid], key, value) - else: - # 添加新条目 - self.add_entry(entry) - logger.info(f"合并世界书: 从 {other_book.name} 合并到 {self.name}") - - def to_sillytavern_json(self, file_path: str) -> None: - """ - 导出为 SillyTavern 格式的 JSON 文件 - - Args: - file_path: 导出文件路径 - """ - # 转换为 SillyTavern 格式 - entries_dict = {} - for uid, entry in self.entries.items(): - entries_dict[uid] = entry.to_sillytavern_dict() - - output_data = { - "entries": entries_dict, - "name": self.name - } - - with open(file_path, 'w', encoding='utf-8') as f: - json.dump(output_data, f, ensure_ascii=False, indent=2) - - logger.info(f"导出世界书为 SillyTavern 格式: 文件={file_path}") - - -# --- 使用示例 --- -if __name__ == "__main__": - try: - # 创建空白世界书 - world_book = WorldBook.create_empty("test_worldbook") - - # 加载世界书 - world_book = WorldBook.load("test_worldbook") - - # 打印概要 - summary = world_book.get_summary() - print(f"世界书名称: {summary['name']}") - print(f"条目数量: {summary['entry_count']}") - print(f"触发策略分布: {summary['trigger_strategies']}") - - # 列出所有条目的触发词和内容预览 - print("\n--- 条目预览 ---") - for item in world_book.list_triggers_and_content(): - triggers = item['triggers'] if item['triggers'] else ['(无关键词 - 常驻)'] - content_preview = item['content'][:50].replace('\n', ' ') + "..." - print(f"[{item['position']}] TRIGGERS: {triggers} -> CONTENT: {content_preview}") - - # 保存世界书 - world_book.save() - print(f"\n✅ 世界书已保存") - - except Exception as e: - print(f"❌ 错误: {e}") diff --git a/backend/core/models/WorldItem.py b/backend/core/models/WorldItem.py deleted file mode 100644 index 44a9100..0000000 --- a/backend/core/models/WorldItem.py +++ /dev/null @@ -1,826 +0,0 @@ -import logging -from enum import Enum -from typing import List, Optional, Dict, Any, Union -from pydantic import BaseModel, Field, field_validator, ConfigDict - -# 配置日志 -logger = logging.getLogger(__name__) - - -class WorldInfoPosition(Enum): - """ - SillyTavern 世界书条目插入位置枚举 - - 注意:枚举值的顺序(0-4)并不完全代表物理顺序! - 以下是按照 Prompt 从上到下的真实物理顺序排列的: - """ - - # --- 1. 顶部区域 --- - # (System Prompt 在这里,不可插入) - - # --- 2. 核心指令区 (Position 4 实际上在这里) --- - SYSTEM_PROMPT = 4 - """ - 物理位置:紧跟在系统提示词之后,角色定义之前。 - 语境:最高优先级的规则。 - 用途:作者注释、核心系统规则。AI 在读人设前就会先读到这个。 - """ - - # --- 3. 角色人设区 (Position 0 实际上在这里) --- - # (Character Definition 在这里) - - CHAR_AFTER = 0 - """ - 物理位置:紧跟在角色定义之后。 - 语境:角色固有属性。 - 用途:性格、外貌、长期设定。 - """ - - # --- 4. 示例对话区 --- - EXAMPLE_BEFORE = 2 - """ - 物理位置:在示例对话块之前。 - """ - - EXAMPLE_AFTER = 3 - """ - 物理位置:在示例对话块之后。 - """ - - # --- 5. 底部区域 --- - # (Chat History 在这里) - # (User Input 在这里 - 最新输入) - - # --- 6. 动态深度区 (Depth / d0-d99) --- - # 这是你强调的"第 6 个插入区" - # 它不是一个固定的物理点,而是一个动态区域 - - DEPTH_HISTORY = 4 - """ - 物理位置: - - d0: 在 [用户最新输入] 之前,[AI 回复] 之前。 - - d0~d99: 在 [Chat History] 内部,倒数第 N 条消息之前。 - - 语境: - - d0: 即时状态("现在正在发生")。 - - d1+: 历史背景("当时就在那里")。 - - 用途: - 这是最灵活的插入区,利用 Depth 字段来精确控制条目在对话流中的位置。 - """ - - @classmethod - def get_description(cls, position: int) -> str: - """ - 获取位置描述 - - Args: - position: 位置值 - - Returns: - str: 位置描述 - """ - position_map = { - 0: "角色定义之后", - 1: "角色定义之后 (最常用)", - 2: "示例对话之前", - 3: "示例对话之后", - 4: "系统提示 / 作者注释 (底部) 或 历史记录深度插入" - } - return position_map.get(position, "未知位置") - - @classmethod - def is_depth_position(cls, position: int) -> bool: - """ - 判断是否为深度插入位置 - - Args: - position: 位置值 - - Returns: - bool: 是否为深度插入位置 - """ - return position == cls.DEPTH_HISTORY.value - - -class TriggerStrategy(str, Enum): - """ - 触发策略枚举 - """ - CONSTANT = "constant" # 永久触发 - KEYWORD = "keyword" # 关键词匹配触发 - RAG = "rag" # 向量检索触发 - CONDITION = "condition" # 逻辑条件触发 - - -class RAGTriggerConfig(BaseModel): - """ - RAG触发配置 - """ - threshold: float = Field(0.75, description="RAG 相似度阈值") - top_k: int = Field(5, description="返回的匹配条目数") - query_template: Optional[str] = Field(None, description="检索用的查询模板") - - -class KeywordTriggerConfig(BaseModel): - """ - 关键词触发配置 - """ - key: List[str] = Field(default_factory=list, description="主关键词数组") - keysecondary: List[str] = Field(default_factory=list, description="次要关键词数组") - selective: bool = Field(True, description="是否开启选择性匹配") - selectiveLogic: int = Field(0, description="逻辑模式 (0=OR, 1=AND)") - matchWholeWords: bool = Field(False, description="是否全词匹配") - caseSensitive: bool = Field(False, description="是否区分大小写") - - -class ConditionTriggerConfig(BaseModel): - """ - 条件触发配置 - """ - variable_a: str = Field(..., description="变量a") - operator: str = Field(..., description="运算符 (>, <, =, >=, <=, !=)") - variable_b: str = Field(..., description="变量b") - - -class TriggerConfig(BaseModel): - """ - 触发配置 - 使用字典结构,键为触发策略,值为[是否启用, 对应配置]的列表 - """ - triggers: Dict[TriggerStrategy, List[ - Union[bool, Optional[Union[KeywordTriggerConfig, RAGTriggerConfig, ConditionTriggerConfig]]]]] = Field( - default_factory=lambda: { - TriggerStrategy.CONSTANT: [True, None], - TriggerStrategy.KEYWORD: [False, None], - TriggerStrategy.RAG: [False, None], - TriggerStrategy.CONDITION: [False, None] - }, - description="触发配置字典,键为触发策略,值为[是否启用, 对应配置]" - ) - - model_config = ConfigDict(extra='forbid') - - def set_trigger(self, strategy: TriggerStrategy, enabled: bool, - config: Optional[Union[KeywordTriggerConfig, RAGTriggerConfig, ConditionTriggerConfig]] = None - ): - """ - 设置触发策略 - - Args: - strategy: 触发策略 - enabled: 是否启用 - config: 对应的配置对象 - """ - self.triggers[strategy] = [enabled, config] - - def get_trigger(self, strategy: TriggerStrategy) -> List[ - Union[bool, Optional[Union[KeywordTriggerConfig, RAGTriggerConfig, ConditionTriggerConfig]]]]: - """ - 获取触发策略 - - Args: - strategy: 触发策略 - - Returns: - List: [是否启用, 对应配置] - """ - return self.triggers.get(strategy, [False, None]) - - def get_enabled_triggers(self) -> List[TriggerStrategy]: - """ - 获取所有启用的触发策略 - - Returns: - List[TriggerStrategy]: 启用的触发策略列表 - """ - return [strategy for strategy, (enabled, _) in self.triggers.items() if enabled] - - -class WorldItem(BaseModel): - """ - 世界书条目完整模型 - 包含所有 SillyTavern 世界书条目属性,用于导入导出 - """ - - class Entry(BaseModel): - """ - 世界书条目模型 - 精简版,只包含必要字段,用于实际使用 - """ - # 基础定义 - uid: int = Field(..., description="唯一标识符") - content: str = Field(..., description="注入到 Prompt 的实际文本内容") - comment: str = Field("", description="条目名、备注") - - # 注入与排序 - position: int = Field(0, - description="插入位置 (0=角色定义之前, 1=角色定义之后, 2=示例对话之前, 3=示例对话之后, 4=系统提示/作者注释)") - order: int = Field(100, description="注入顺序权重,数字越小优先级越高") - depth: int = Field(4, description="扫描深度,0为最深/最高,4为标准") - - # 触发配置 - trigger_config: Optional[TriggerConfig] = Field( - default_factory=TriggerConfig, - description="触发配置,为空表示无需触发配置" - ) - # 角色匹配 - role: int = Field(0, description="角色匹配 (0=Both, 1=User, 2=Assistant)") - - # 条目启用状态 - enabled: bool = Field(True, description="条目是否启用(启用才会被插入到LLM)") - - @field_validator('position') - @classmethod - def validate_position(cls, v): - """验证 position 值是否在有效范围内""" - if v not in [0, 1, 2, 3, 4]: - logger.warning(f"无效的 position 值: {v},将使用默认值 1") - return 1 - return v - - def to_dict(self) -> Dict[str, Any]: - """ - 转换为字典 - - Returns: - Dict[str, Any]: 字典数据 - """ - return self.dict() - - def get_trigger_params(self) -> Dict[str, Any]: - """ - 获取触发策略所需的参数 - - Returns: - Dict[str, Any]: 触发参数字典 - """ - params = {} - - try: - # 获取所有启用的触发策略 - enabled_triggers = self.trigger_config.get_enabled_triggers() - - # 处理 RAG 触发 - if TriggerStrategy.RAG in enabled_triggers: - _, rag_config = self.trigger_config.get_trigger(TriggerStrategy.RAG) - if rag_config: - params["threshold"] = rag_config.threshold - params["top_k"] = rag_config.top_k - params["query_template"] = rag_config.query_template - params["vectorized"] = True - - # 处理关键词触发 - if TriggerStrategy.KEYWORD in enabled_triggers: - _, keyword_config = self.trigger_config.get_trigger(TriggerStrategy.KEYWORD) - if keyword_config: - params["key"] = keyword_config.key - params["keysecondary"] = keyword_config.keysecondary - params["selective"] = keyword_config.selective - params["selectiveLogic"] = keyword_config.selectiveLogic - params["matchWholeWords"] = keyword_config.matchWholeWords - params["caseSensitive"] = keyword_config.caseSensitive - - # 处理条件触发 - if TriggerStrategy.CONDITION in enabled_triggers: - _, condition_config = self.trigger_config.get_trigger(TriggerStrategy.CONDITION) - if condition_config: - params["variable_a"] = condition_config.variable_a - params["operator"] = condition_config.operator - params["variable_b"] = condition_config.variable_b - except Exception as e: - # 如果获取触发参数失败,返回空字典,表示使用默认的永久触发 - logger.warning(f"条目 {self.uid} 的触发参数获取失败: {str(e)},使用默认的永久触发") - - return params - - # 基础定义 - uid: int = Field(..., description="唯一标识符") - content: str = Field(..., description="注入到 Prompt 的实际文本内容") - comment: str = Field("", description="条目名、备注") - - # 注入与排序 - position: int = Field(0, - description="插入位置 (0=角色定义之前, 1=角色定义之后, 2=示例对话之前, 3=示例对话之后, 4=系统提示/作者注释)") - order: int = Field(100, description="注入顺序权重,数字越小优先级越高") - depth: int = Field(4, description="扫描深度,0为最深/最高,4为标准") - - # 触发配置 - trigger_config: TriggerConfig = Field( - default_factory=TriggerConfig, - description="触发配置" - ) - - # 角色匹配 - role: int = Field(0, description="角色匹配 (0=Both, 1=User, 2=Assistant)") - - # 条目启用状态 - enabled: bool = Field(True, description="条目是否启用(启用才会被插入到LLM)") - - # 触发相关属性 - vectorized: bool = Field(False, description="是否使用向量检索(RAG触发)") - selective: bool = Field(True, description="是否开启选择性匹配(关键词触发)") - selectiveLogic: int = Field(0, description="逻辑模式 (0=OR, 1=AND)") - constant: bool = Field(False, description="是否永久触发") - - # 关键词相关 - key: List[str] = Field(default_factory=list, description="主关键词数组") - keysecondary: List[str] = Field(default_factory=list, description="次要关键词数组") - matchWholeWords: Optional[bool] = Field(None, description="是否全词匹配") - caseSensitive: Optional[bool] = Field(None, description="是否区分大小写") - - # RAG相关 - rag_threshold: Optional[float] = Field(None, description="RAG 相似度阈值") - top_k: Optional[int] = Field(None, description="返回的匹配条目数") - query_template: Optional[str] = Field(None, description="检索用的查询模板") - - # 条目控制 - addMemo: bool = Field(True, description="是否添加备忘") - disable: bool = Field(False, description="是否禁用") - ignoreBudget: bool = Field(False, description="是否忽略预算") - excludeRecursion: bool = Field(True, description="是否排除递归") - preventRecursion: bool = Field(True, description="是否阻止递归") - matchPersonaDescription: bool = Field(False, description="是否匹配人设描述") - matchCharacterDescription: bool = Field(False, description="是否匹配角色描述") - matchCharacterPersonality: bool = Field(False, description="是否匹配角色性格") - matchCharacterDepthPrompt: bool = Field(False, description="是否匹配深度提示") - matchScenario: bool = Field(False, description="是否匹配场景") - matchCreatorNotes: bool = Field(False, description="是否匹配作者笔记") - delayUntilRecursion: bool = Field(False, description="是否延迟递归") - - # 概率相关 - probability: int = Field(100, description="触发概率 (0-100)") - useProbability: bool = Field(True, description="是否使用概率") - - # 分组相关 - group: str = Field("", description="分组名称") - groupOverride: bool = Field(False, description="是否覆盖分组") - groupWeight: int = Field(100, description="分组权重") - useGroupScoring: bool = Field(False, description="是否使用分组评分") - - # 其他属性 - scanDepth: Optional[int] = Field(None, description="扫描深度") - automationId: str = Field("", description="自动化ID") - sticky: int = Field(0, description="粘性") - cooldown: int = Field(0, description="冷却时间(秒)") - delay: int = Field(0, description="延迟时间(秒)") - displayIndex: int = Field(0, description="显示索引") - - # 角色过滤器 - characterFilter: Dict[str, Any] = Field( - default_factory=lambda: {"isExclude": False, "names": [], "tags": []}, - description="角色过滤器" - ) - - # 验证器 - @field_validator('position') - @classmethod - def validate_position(cls, v): - """验证 position 值是否在有效范围内""" - if v not in [0, 1, 2, 3, 4]: - logger.warning(f"无效的 position 值: {v},将使用默认值 1") - return 1 - return v - - @field_validator('role') - @classmethod - def validate_role(cls, v): - """验证 role 值是否在有效范围内""" - if v not in [0, 1, 2]: - logger.warning(f"无效的 role 值: {v},将使用默认值 2") - return 2 - return v - - @classmethod - def from_dict(cls, data: Dict[str, Any]) -> 'WorldItem': - """ - 从字典创建 WorldItem 对象 - - Args: - data: 字典数据 - - Returns: - WorldItem: WorldItem 对象 - """ - return cls(**data) - - def to_dict(self) -> Dict[str, Any]: - """ - 转换为字典 - - Returns: - Dict[str, Any]: 字典数据 - """ - return self.dict() - - def to_entry(self) -> Entry: - """ - 转换为 Entry 对象 - - Returns: - Entry: Entry 对象 - """ - # 转换为 SillyTavern 格式的字典 - sillytavern_dict = self.to_sillytavern_dict() - # 创建 Entry 对象 - return self.Entry(**sillytavern_dict) - - def to_sillytavern_dict(self) -> Dict[str, Any]: - """ - 转换为 SillyTavern 格式的字典 - - Returns: - Dict[str, Any]: SillyTavern 格式的条目数据 - """ - result = { - "uid": self.uid, - "content": self.content, - "comment": self.comment, - "position": self.position, - "order": self.order, - "depth": self.depth, - "role": self.role, - "enabled": self.enabled, - "vectorized": self.vectorized, - "selective": self.selective, - "selectiveLogic": self.selectiveLogic, - "constant": self.constant, - "key": self.key, - "keysecondary": self.keysecondary, - "matchWholeWords": self.matchWholeWords, - "caseSensitive": self.caseSensitive, - "addMemo": self.addMemo, - "disable": self.disable, - "ignoreBudget": self.ignoreBudget, - "excludeRecursion": self.excludeRecursion, - "preventRecursion": self.preventRecursion, - "matchPersonaDescription": self.matchPersonaDescription, - "matchCharacterDescription": self.matchCharacterDescription, - "matchCharacterPersonality": self.matchCharacterPersonality, - "matchCharacterDepthPrompt": self.matchCharacterDepthPrompt, - "matchScenario": self.matchScenario, - "matchCreatorNotes": self.matchCreatorNotes, - "delayUntilRecursion": self.delayUntilRecursion, - "probability": self.probability, - "useProbability": self.useProbability, - "group": self.group, - "groupOverride": self.groupOverride, - "groupWeight": self.groupWeight, - "scanDepth": self.scanDepth, - "automationId": self.automationId, - "sticky": self.sticky, - "cooldown": self.cooldown, - "delay": self.delay, - "displayIndex": self.displayIndex, - "characterFilter": self.characterFilter - } - - # 添加 RAG 相关字段 - if self.vectorized: - result["rag_threshold"] = self.rag_threshold - result["top_k"] = self.top_k - result["query_template"] = self.query_template - - # 添加条件触发相关字段 - if TriggerStrategy.CONDITION in self.trigger_config.get_enabled_triggers(): - condition_config = self.trigger_config.get_trigger(TriggerStrategy.CONDITION)[1] - if condition_config: - result["variable_a"] = condition_config.variable_a - result["operator"] = condition_config.operator - result["variable_b"] = condition_config.variable_b - - return result - - @classmethod - def from_sillytavern_data(cls, data: Dict[str, Any]) -> 'WorldItem': - """ - 从 SillyTavern 格式的数据创建 WorldItem 对象 - - Args: - data: SillyTavern 格式的条目数据 - - Returns: - WorldItem: WorldItem 对象 - """ - - constant = data.get("constant", False) - if isinstance(constant, str): - constant = constant.lower() in ('true', '1', 'yes') - - enabled = data.get("enabled", True) - if isinstance(enabled, str): - enabled = enabled.lower() in ('true', '1', 'yes') - - try: - # 提取必要字段 - uid = int(data.get("uid", data.get("id", 0))) - content = data.get("content", "") - comment = data.get("comment", "") - position = data.get("position", 0) - order = data.get("order", 100) - depth = data.get("depth", 4) - role = data.get("role", 0) - enabled = data.get("enabled", True) - - # 处理 position 字段,确保为整数类型 - if isinstance(position, str): - try: - position = int(position) - except ValueError: - logger.warning(f"条目 {uid} 的 position 字段值 '{position}' 无法转换为整数,使用默认值 0") - position = 0 - - # 初始化触发配置 - trigger_config = TriggerConfig() - - # 读取触发相关字段,并进行类型转换 - vectorized = data.get("vectorized", False) - if isinstance(vectorized, str): - vectorized = vectorized.lower() in ('true', '1', 'yes') - - selective = data.get("selective", True) - if isinstance(selective, str): - selective = selective.lower() in ('true', '1', 'yes') - - constant = data.get("constant", False) - if isinstance(constant, str): - constant = constant.lower() in ('true', '1', 'yes') - - # 初始化变量,确保它们始终有值 - key = [] - keysecondary = [] - selectiveLogic = 0 - matchWholeWords = False - caseSensitive = False - - # 判断触发策略并设置对应的触发配置 - # 优先级:vectorized > constant > selective - if vectorized: - # RAG 触发 - rag_config = RAGTriggerConfig( - threshold=float(data.get("rag_threshold", 0.75)), - top_k=int(data.get("top_k", 5)), - query_template=data.get("query_template", None) - ) - trigger_config.set_trigger(TriggerStrategy.RAG, True, rag_config) - elif constant: - # 永久触发 - trigger_config.set_trigger(TriggerStrategy.CONSTANT, True) - elif selective: - # 关键词触发 - key = data.get("key", []) - keysecondary = data.get("keysecondary", data.get("secondary_keys", [])) - selectiveLogic = int(data.get("selectiveLogic", 0)) - - # 处理 matchWholeWords 字段 - matchWholeWords = data.get("matchWholeWords", False) - if matchWholeWords is None: - matchWholeWords = False - elif isinstance(matchWholeWords, str): - matchWholeWords = matchWholeWords.lower() in ('true', '1', 'yes') - - # 处理 caseSensitive 字段 - caseSensitive = data.get("caseSensitive", False) - if caseSensitive is None: - caseSensitive = False - elif isinstance(caseSensitive, str): - caseSensitive = caseSensitive.lower() in ('true', '1', 'yes') - - keyword_config = KeywordTriggerConfig( - key=key, - keysecondary=keysecondary, - selective=selective, - selectiveLogic=selectiveLogic, - matchWholeWords=matchWholeWords, - caseSensitive=caseSensitive - ) - trigger_config.set_trigger(TriggerStrategy.KEYWORD, True, keyword_config) - else: - # 默认使用永久触发 - trigger_config.set_trigger(TriggerStrategy.CONSTANT, True) - - # 检查是否有条件触发(虽然 JSON 中没有对应字段,但需要保留兼容性) - if "variable_a" in data and "operator" in data and "variable_b" in data: - condition_config = ConditionTriggerConfig( - variable_a=data.get("variable_a", ""), - operator=data.get("operator", "="), - variable_b=data.get("variable_b", "") - ) - trigger_config.set_trigger(TriggerStrategy.CONDITION, True, condition_config) - - # 创建 WorldItem 对象 - return cls( - uid=uid, - content=content, - comment=comment, - position=position, - order=order, - depth=depth, - trigger_config=trigger_config, - role=role, - enabled=enabled, - vectorized=vectorized, - selective=selective, - selectiveLogic=selectiveLogic, - constant=constant, - key=key, - keysecondary=keysecondary, - matchWholeWords=matchWholeWords, - caseSensitive=caseSensitive, - rag_threshold=float(data.get("rag_threshold", None)) if vectorized else None, - top_k=int(data.get("top_k", None)) if vectorized else None, - query_template=data.get("query_template", None), - addMemo=data.get("addMemo", True), - disable=data.get("disable", False), - ignoreBudget=data.get("ignoreBudget", False), - excludeRecursion=data.get("excludeRecursion", True), - preventRecursion=data.get("preventRecursion", True), - matchPersonaDescription=data.get("matchPersonaDescription", False), - matchCharacterDescription=data.get("matchCharacterDescription", False), - matchCharacterPersonality=data.get("matchCharacterPersonality", False), - matchCharacterDepthPrompt=data.get("matchCharacterDepthPrompt", False), - matchScenario=data.get("matchScenario", False), - matchCreatorNotes=data.get("matchCreatorNotes", False), - delayUntilRecursion=data.get("delayUntilRecursion", False), - probability=data.get("probability", 100), - useProbability=data.get("useProbability", True), - group=data.get("group", ""), - groupOverride=data.get("groupOverride", False), - groupWeight=data.get("groupWeight", 100), - useGroupScoring=data.get("useGroupScoring", False), - scanDepth=data.get("scanDepth", None), - automationId=data.get("automationId", ""), - sticky=data.get("sticky", 0), - cooldown=data.get("cooldown", 0), - delay=data.get("delay", 0), - displayIndex=data.get("displayIndex", 0), - characterFilter=data.get("characterFilter", {"isExclude": False, "names": [], "tags": []}) - ) - except Exception as e: - logger.error(f"从 SillyTavern 数据创建 WorldItem 失败: {str(e)}") - raise ValueError(f"从 SillyTavern 数据创建 WorldItem 失败: {str(e)}") - - -if __name__ == "__main__": - """ - 测试入口:用于调试 WorldItem 的解析和转换功能 - 可以像断点调试一样查看内部执行过程 - """ - import json - import sys - from pathlib import Path - - # 测试用例1:基本条目 - test_data_1 = { - "uid": 0, - "content": "测试内容", - "comment": "测试条目", - "position": 0, - "order": 100, - "depth": 4, - "role": 0, - "vectorized": False, - "selective": True, - "selectiveLogic": 0, - "constant": False, - "key": ["测试关键词"], - "keysecondary": [], - "matchWholeWords": False, - "caseSensitive": False, - "addMemo": True, - "disable": False, - "ignoreBudget": False, - "excludeRecursion": True, - "preventRecursion": True - } - - # 测试用例2:RAG触发 - test_data_2 = { - "uid": 1, - "content": "RAG测试内容", - "comment": "RAG测试条目", - "position": 4, - "order": 50, - "depth": 0, - "role": 0, - "vectorized": True, - "rag_threshold": 0.8, - "top_k": 10, - "query_template": "测试模板" - } - - # 测试用例3:条件触发 - test_data_3 = { - "uid": 2, - "content": "条件触发测试", - "comment": "条件触发条目", - "position": 1, - "order": 75, - "depth": 2, - "role": 0, - "variable_a": "好感度", - "operator": ">", - "variable_b": "50" - } - - print("=" * 60) - print("开始测试 WorldItem 解析功能") - print("=" * 60) - - try: - # 测试1:解析基本条目 - print("\n【测试1】解析基本条目...") - item1 = WorldItem.from_sillytavern_data(test_data_1) - print(f"✓ 解析成功: {item1.comment}") - print(f" - UID: {item1.uid}") - print(f" - Position: {item1.position}") - print(f" - 触发策略和配置:") - for strategy in item1.trigger_config.get_enabled_triggers(): - enabled, config = item1.trigger_config.get_trigger(strategy) - print(f" * {strategy.value}: 启用={enabled}, 配置={config}") - - # 测试2:解析RAG触发条目 - print("\n【测试2】解析RAG触发条目...") - item2 = WorldItem.from_sillytavern_data(test_data_2) - print(f"✓ 解析成功: {item2.comment}") - print(f" - UID: {item2.uid}") - print(f" - Position: {item2.position}") - print(f" - 触发策略和配置:") - for strategy in item2.trigger_config.get_enabled_triggers(): - enabled, config = item2.trigger_config.get_trigger(strategy) - print(f" * {strategy.value}: 启用={enabled}, 配置={config}") - if item2.rag_threshold: - print(f" - RAG阈值: {item2.rag_threshold}") - - # 测试3:解析条件触发条目 - print("\n【测试3】解析条件触发条目...") - item3 = WorldItem.from_sillytavern_data(test_data_3) - print(f"✓ 解析成功: {item3.comment}") - print(f" - UID: {item3.uid}") - print(f" - Position: {item3.position}") - print(f" - 触发策略和配置:") - for strategy in item3.trigger_config.get_enabled_triggers(): - enabled, config = item3.trigger_config.get_trigger(strategy) - print(f" * {strategy.value}: 启用={enabled}, 配置={config}") - - # 测试4:从文件读取实际数据 - print("\n【测试4】从实际JSON文件读取...") - # 从当前文件位置向上查找项目根目录 - current_file = Path(__file__).resolve() - project_root = current_file - while project_root.name != "llm_workflow_engine" and project_root.parent != project_root: - project_root = project_root.parent - - # 构建正确的文件路径 - json_path = project_root / "data" / "worldbooks" / "卡立创-v5.json" - print(f"查找文件路径: {json_path}") - - if json_path.exists(): - with open(json_path, 'r', encoding='utf-8') as f: - worldbook_data = json.load(f) - entries = worldbook_data.get('entries', {}) - print(f"找到 {len(entries)} 个条目") - - # 只测试前3个条目 - for uid, entry_data in list(entries.items())[:3]: - try: - item = WorldItem.from_sillytavern_data(entry_data) - print(f"\n✓ 条目 {uid} 解析成功:") - print(f" - 备注: {item.comment}") - print(f" - UID: {item.uid}") - print(f" - Position: {item.position}") - print(f" - 触发策略和配置:") - for strategy in item.trigger_config.get_enabled_triggers(): - enabled, config = item.trigger_config.get_trigger(strategy) - print(f" * {strategy.value}: 启用={enabled}, 配置={config}") - except Exception as e: - print(f"\n✗ 条目 {uid} 解析失败: {str(e)}") - import traceback - - traceback.print_exc() - else: - print(f"⚠ 文件不存在: {json_path}") - print(f"请确认文件路径是否正确") - # 列出可能的文件位置 - possible_paths = [ - project_root / "data" / "worldbooks", - project_root / "backend" / "data" / "worldbooks", - current_file.parent.parent.parent / "data" / "worldbooks" - ] - print("\n可能的文件位置:") - for path in possible_paths: - if path.exists(): - print(f" ✓ {path}") - for file in path.glob("*.json"): - print(f" - {file.name}") - - print("\n" + "=" * 60) - print("所有测试完成!") - print("=" * 60) - - except Exception as e: - print(f"\n✗ 测试失败: {str(e)}") - import traceback - - traceback.print_exc() - sys.exit(1) diff --git a/backend/core/models/chat_history.py b/backend/core/models/chat_history.py deleted file mode 100644 index 57881e0..0000000 --- a/backend/core/models/chat_history.py +++ /dev/null @@ -1,443 +0,0 @@ -from typing import List, Dict, Any, Optional -from datetime import datetime -from pathlib import Path -from pydantic import BaseModel, Field -import json - - -class Message(BaseModel): - """消息类,代表JSONL文件中的一行消息内容""" - name: str = Field(..., description="发送者名称") - is_user: bool = Field(..., description="是否为用户消息") - is_system: bool = Field(False, description="是否为系统消息") - send_date: str = Field( - default_factory=lambda: str(int(datetime.now().timestamp() * 1000)), - description="消息发送时间戳" - ) - floor: int = Field(0, description="对话楼层数") - swipes: List[str] = Field( - default_factory=list, - description="历史版本列表。用户消息:存编辑过的不同版本。AI消息:存重roll生成的不同版本" - ) - swipe_id: int = Field( - 0, - description="当前指针。指示当前显示的是 swipes 数组中的第几个(从 0 开始)" - ) - mes: str = Field(..., description="消息内容文本") - extra: Dict[str, Any] = Field( - default_factory=dict, - description="额外信息,包含推理内容、API、模型等" - ) - force_avatar: Optional[str] = Field(None, description="强制头像URL") - variables: List[Any] = Field(default_factory=list, description="消息变量列表") - variables_initialized: List[bool] = Field(default_factory=list, description="变量初始化状态数组") - is_ejs_processed: List[bool] = Field(default_factory=list, description="EJS处理状态数组") - - # 以下属性仅在is_user为False时有值 - api: Optional[str] = Field(None, description="使用的API提供商") - model: Optional[str] = Field(None, description="使用的AI模型") - reasoning: Optional[str] = Field(None, description="推理内容") - reasoning_duration: Optional[float] = Field(None, description="推理耗时") - reasoning_signature: Optional[str] = Field(None, description="推理签名") - time_to_first_token: Optional[float] = Field(None, description="首Token响应时间") - bias: Optional[float] = Field(None, description="偏差值") - - -class ChatMetadata(BaseModel): - """聊天元数据类,包含整个聊天的共享属性""" - user_name: str = Field("User", description="用户名称") - character_name: str = Field("Assistant", description="角色名称") - - # 完整性校验相关 - integrity: str = Field("", description="完整性校验值") - chat_id_hash: str = Field("", description="聊天ID哈希值") - - # 笔记相关 - note_prompt: str = Field("", description="作者笔记提示词") - note_interval: int = Field(0, description="笔记插入间隔数") - note_position: int = Field(0, description="笔记插入位置") - note_depth: int = Field(0, description="笔记插入深度") - # 0:System,1:User,2:Assistant - note_role: int = Field("", description="笔记使用角色类型") - - # 扩展信息 - extensions: Dict[str, Any] = Field( - default_factory=dict, - description="扩展信息,如LittleWhiteBox等" - ) - # 世界信息 - timedWorldInfo: Dict[str, Any] = Field( - default_factory=dict, - description="定时世界信息" - ) - # 变量 - variables: Dict[str, Any] = Field( - default_factory=dict, - description="变量字典" - ) - # 状态标记 - tainted: bool = Field(False, description="是否被修改标记") - lastInContextMessageId: int = Field(-1, description="最后上下文消息ID") - - -class ChatHistory(BaseModel): - """聊天文件类,包含完整的聊天记录""" - chat_metadata: ChatMetadata = Field(..., description="聊天元数据,包含基本信息和配置") - messages: List[Message] = Field(default_factory=list, description="消息列表") - - class Config: - arbitrary_types_allowed = True - - @classmethod - def get_data_path(cls) -> Path: - """获取数据目录路径""" - try: - from backend.core.config import settings - return settings.DATA_PATH / "chat" - except ImportError: - return Path("data") - - @classmethod - async def list_all_chats(cls) -> Dict[str, List[Dict]]: - """获取所有角色的所有聊天列表""" - data_dir = cls.get_data_path() - if not data_dir.exists(): - return {"chat": []} - - chats = [] - for role_dir in data_dir.iterdir(): - if role_dir.is_dir(): - for chat_file in role_dir.glob("*.jsonl"): - try: - with open(chat_file, 'r', encoding='utf-8') as f: - # 读取第一行获取元数据 - first_line = f.readline() - metadata = json.loads(first_line) - chats.append({ - "role_name": role_dir.name, - "chat_name": chat_file.stem, - "user_name": metadata.get("user_name", "User"), - "character_name": metadata.get("character_name", "Assistant"), - "last_modified": metadata.get("last_modified", ""), - "message_count": sum(1 for _ in f) # 统计剩余行数(消息数) - }) - except Exception: - continue # 跳过损坏的聊天文件 - return {"chat": chats} - - @classmethod - async def get_chat(cls, role_name: str, chat_name: str) -> Dict[str, Any]: - """获取指定聊天的完整内容""" - chat_history = cls.load_from_file(role_name, chat_name) - return { - "metadata": chat_history.chat_metadata.dict(), - "messages": chat_history.to_chatbox_format() - } - - @classmethod - async def create_chat(cls, role_name: str, chat_name: str, metadata: Optional[Dict] = None) -> Dict[str, str]: - """创建新聊天""" - base_path = cls.get_data_path() - role_dir = base_path / role_name - role_dir.mkdir(parents=True, exist_ok=True) - chat_path = role_dir / f"{chat_name}.jsonl" - - if chat_path.exists(): - raise FileExistsError(f"Chat already exists: {chat_path}") - - # 创建聊天历史对象 - chat_history = cls( - chat_metadata=ChatMetadata(**(metadata or {})), - messages=[] - ) - - # 保存到文件 - chat_history.save_to_file(role_name, chat_name, base_path) - return {"message": "Chat created successfully"} - - @classmethod - async def update_chat(cls, role_name: str, chat_name: str, update_data: Dict) -> Dict[str, str]: - """更新聊天元数据""" - chat_history = cls.load_from_file(role_name, chat_name) - - # 更新元数据 - if "metadata" in update_data: - for key, value in update_data["metadata"].items(): - if hasattr(chat_history.chat_metadata, key): - setattr(chat_history.chat_metadata, key, value) - - # 保存更改 - base_path = cls.get_data_path() - chat_history.save_to_file(role_name, chat_name, base_path) - return {"message": "Chat metadata updated successfully"} - - @classmethod - async def delete_chat(cls, role_name: str, chat_name: str) -> Dict[str, str]: - """删除指定聊天""" - base_path = cls.get_data_path() - chat_path = base_path / role_name / f"{chat_name}.jsonl" - - if not chat_path.exists(): - raise FileNotFoundError(f"Chat not found: {chat_path}") - - chat_path.unlink() - return {"message": "Chat deleted successfully"} - - @classmethod - async def list_messages(cls, role_name: str, chat_name: str) -> Dict[str, List[Dict]]: - """获取聊天的所有消息""" - chat_history = cls.load_from_file(role_name, chat_name) - return {"messages": chat_history.to_chatbox_format()} - - @classmethod - async def get_message(cls, role_name: str, chat_name: str, floor: int) -> Dict[str, Any]: - """获取指定楼层的消息""" - chat_history = cls.load_from_file(role_name, chat_name) - message = next((msg for msg in chat_history.messages if msg.floor == floor), None) - - if not message: - raise FileNotFoundError(f"Message not found: floor {floor}") - - return message.dict() - - @classmethod - async def add_message(cls, role_name: str, chat_name: str, message_data: Dict) -> Dict[str, Any]: - """向聊天添加新消息""" - chat_history = cls.load_from_file(role_name, chat_name) - - # 创建消息对象 - message = Message(**message_data) - - # 检查楼层是否已存在 - if any(msg.floor == message.floor for msg in chat_history.messages): - raise ValueError(f"Message floor already exists: {message.floor}") - - # 添加消息 - chat_history.messages.append(message) - - # 保存更改 - base_path = cls.get_data_path() - chat_history.save_to_file(role_name, chat_name, base_path) - return {"message": "Message added successfully", "floor": message.floor} - - @classmethod - async def update_message(cls, role_name: str, chat_name: str, floor: int, update_data: Dict) -> Dict[str, str]: - """更新指定楼层的消息""" - chat_history = cls.load_from_file(role_name, chat_name) - message = next((msg for msg in chat_history.messages if msg.floor == floor), None) - - if not message: - raise FileNotFoundError(f"Message not found: floor {floor}") - - # 更新消息字段 - for key, value in update_data.items(): - if hasattr(message, key): - setattr(message, key, value) - - # 保存更改 - base_path = cls.get_data_path() - chat_history.save_to_file(role_name, chat_name, base_path) - return {"message": "Message updated successfully"} - - @classmethod - async def delete_message(cls, role_name: str, chat_name: str, floor: int) -> Dict[str, str]: - """删除指定楼层的消息""" - chat_history = cls.load_from_file(role_name, chat_name) - - # 查找并删除消息 - original_length = len(chat_history.messages) - chat_history.messages = [msg for msg in chat_history.messages if msg.floor != floor] - - if len(chat_history.messages) == original_length: - raise FileNotFoundError(f"Message not found: floor {floor}") - - # 保存更改 - base_path = cls.get_data_path() - chat_history.save_to_file(role_name, chat_name, base_path) - return {"message": "Message deleted successfully"} - - @classmethod - def load_from_file(cls, role_name: str, chat_name: str, base_path: Path = None) -> 'ChatHistory': - """ - 从JSONL文件加载聊天历史 - - 参数: - role_name: 角色名称(文件夹名) - chat_name: 聊天名称(文件名,不含扩展名) - base_path: 基础路径,默认为配置中的DATA_PATH/chat - - 返回: - ChatHistory: 加载的聊天历史对象 - - 异常: - FileNotFoundError: 当文件不存在时抛出 - json.JSONDecodeError: 当JSON解析失败时抛出 - """ - # 设置默认基础路径 - if base_path is None: - base_path = cls.get_data_path() - - # 构建文件路径 - file_path = base_path / role_name / f"{chat_name}.jsonl" - - # 检查文件是否存在 - if not file_path.exists(): - raise FileNotFoundError(f"聊天文件不存在: {file_path}") - - # 初始化结果数据 - messages = [] - metadata = None - - # 读取文件内容 - with open(file_path, 'r', encoding='utf-8') as f: - for line_num, line in enumerate(f): - try: - line_data = json.loads(line.strip()) - - # 第一行是元数据 - if line_num == 0: - metadata = ChatMetadata(**line_data) - else: - # 后续行是消息 - messages.append(Message(**line_data)) - except json.JSONDecodeError: - continue - - # 创建并返回ChatHistory对象 - return cls( - chat_metadata=metadata or ChatMetadata(), - messages=messages - ) - - @classmethod - def load_from_jsonl(cls, file_path: Path) -> 'ChatHistory': - """ - 从JSONL文件加载聊天历史 - - 参数: - file_path: JSONL文件路径 - - 返回: - ChatHistory: 加载的聊天历史对象 - - 异常: - FileNotFoundError: 当文件不存在时抛出 - json.JSONDecodeError: 当JSON解析失败时抛出 - """ - # 检查文件是否存在 - if not file_path.exists(): - raise FileNotFoundError(f"聊天文件不存在: {file_path}") - - # 初始化结果数据 - messages = [] - metadata = None - - # 读取文件内容 - with open(file_path, 'r', encoding='utf-8') as f: - for line_num, line in enumerate(f): - try: - line_data = json.loads(line.strip()) - - # 第一行是元数据 - if line_num == 0: - # 处理元数据中的嵌套结构 - if 'chat_metadata' in line_data: - metadata_dict = line_data['chat_metadata'] - # 合并顶层字段和chat_metadata中的字段 - metadata_dict.update(line_data) - metadata = ChatMetadata(**metadata_dict) - else: - metadata = ChatMetadata(**line_data) - else: - # 后续行是消息 - # 处理extra字段中的内容 - extra_data = line_data.get('extra', {}) - - # 如果是AI消息(is_user=False),将extra中的某些字段提升到顶层 - if not line_data.get('is_user', True): - ai_fields = ['api', 'model', 'reasoning', 'reasoning_duration', - 'reasoning_signature', 'time_to_first_token', 'bias'] - for field in ai_fields: - if field in extra_data: - line_data[field] = extra_data.pop(field) - - # 创建Message实例 - message = Message(**line_data) - # 将剩余的extra数据保存回extra字段 - message.extra = extra_data - messages.append(message) - - except json.JSONDecodeError: - continue - - # 创建并返回ChatHistory对象 - return cls( - chat_metadata=metadata or ChatMetadata(), - messages=messages - ) - - def to_chatbox_format(self) -> List[Dict[str, Any]]: - """ - 将聊天历史转换为适合前端chatbox显示的格式 - - 返回: - List[Dict[str, Any]]: 按floor排序的消息字典列表,每个字典包含: - { - "name": str, - "is_user": bool, - "floor": int, - "mes": str, - "swipes": List[str], - "swipe_id": int - } - """ - # 创建消息字典列表 - messages_list = [] - for msg in self.messages: - # 获取当前消息内容:优先从swipes数组中获取,如果不存在则使用mes - current_mes = msg.mes - if msg.swipes and 0 <= msg.swipe_id < len(msg.swipes): - current_mes = msg.swipes[msg.swipe_id] - - msg_dict = { - "name": msg.name, - "is_user": msg.is_user, - "floor": msg.floor, - "mes": current_mes, - "swipes": msg.swipes, - "swipe_id": msg.swipe_id - } - messages_list.append(msg_dict) - - # 按floor排序 - messages_list.sort(key=lambda x: x["floor"]) - - return messages_list - - def save_to_file(self, role_name: str, chat_name: str, base_path: Path = None) -> None: - """ - 将聊天历史保存到JSONL文件 - - 参数: - role_name: 角色名称(文件夹名) - chat_name: 聊天名称(文件名,不含扩展名) - base_path: 基础路径,默认为data/chat - """ - # 设置默认基础路径 - if base_path is None: - base_path = self.get_data_path() - - # 构建文件路径 - file_path = base_path / role_name / f"{chat_name}.jsonl" - - # 确保目录存在 - file_path.parent.mkdir(parents=True, exist_ok=True) - - # 写入文件 - with open(file_path, 'w', encoding='utf-8') as f: - # 写入元数据 - f.write(json.dumps(self.chat_metadata.dict(), ensure_ascii=False) + '\n') - - # 写入消息 - for message in self.messages: - f.write(json.dumps(message.dict(), ensure_ascii=False) + '\n') diff --git a/backend/main.py b/backend/main.py index bb49321..0bd088a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -17,12 +17,25 @@ for logger_name in ['uvicorn', 'uvicorn.access', 'fastapi']: # backend/app/main.py from fastapi import FastAPI -from .api.route import router +try: + from backend.api.route import router +except ImportError: + from api.route import router app = FastAPI(title="LLM Workflow Engine") # 注册路由 app.include_router(router, prefix="/api") +# 添加健康检查端点 +@app.get("/health") +async def health_check(): + return {"status": "healthy"} + +# 添加根路径 +@app.get("/") +async def root(): + return {"message": "LLM Workflow Engine", "status": "running"} + if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/backend/nodes/PresetAssemblyNode.py b/backend/nodes/PresetAssemblyNode.py deleted file mode 100644 index b03932c..0000000 --- a/backend/nodes/PresetAssemblyNode.py +++ /dev/null @@ -1,231 +0,0 @@ -from typing import List, Dict, Any, Optional -from pydantic import BaseModel, Field -from backend.core.models.PromptList import AIDesignSpec -from backend.core.models.PromptComponent import PromptComponent -from enum import Enum - -from enum import Enum - - -class SpecialIdentifier(str, Enum): - """ - 特殊组件标识符枚举 - 定义所有提示词组件的类型及其在最终 Prompt 中的默认物理流向 - 顺序大致遵循:系统层 -> 角色层 -> 动态层 -> 历史层 -> 尾部指令 - """ - - WORLD_INFO_BEFORE = "worldInfoBefore" - """前置世界书:通常用于全局设定(如物理法则),紧接在 Main Prompt 之后,拥有最高优先级""" - - PERSONA_DESCRIPTION = "personaDescription" - """用户设定:告诉 AI {{user}} 是谁,通常放在场景之后,完成“谁在对谁说话”的闭环""" - - ENHANCE_DEFINITIONS = "enhanceDefinitions" - """增强定义:通常是 "If you have more knowledge...",用于补充 AI 的知识库,这里用rag获取""" - - WORLD_INFO_AFTER = "worldInfoAfter" - """后置世界书:通常用于特定场景规则,位于中间层底部,用于覆盖或补充前面的全局设定""" - - CHAT_HISTORY = "chatHistory" - """聊天历史:包含用户与 AI 的过往对话,占据提示词的下半部分""" - - JAILBREAK = "jailbreak" - """后置指令/注释,也即d0层:通常位于聊天记录之后、AI 生成之前,用于最后时刻的强调(如“不要重复”)""" - -class PresetAssemblyNode(BaseModel): - """预设组装节点类,负责根据组装指令动态组装提示词内容""" - - # 输入数据 - design_spec: AIDesignSpec = Field( - ..., - description="AI设计规范,包含组件库和组装顺序" - ) - target_character_id: int = Field( - ..., - description="目标角色ID,用于选择对应的组装指令" - ) - - # 内部状态(不参与序列化) - _component_map: Dict[str, PromptComponent] = Field( - default_factory=dict, - description="组件标识符到组件对象的映射" - ) - - def __init__(self, **data): - """初始化方法,构建组件映射""" - super().__init__(**data) - # 构建组件映射字典,提高查找效率 - self._component_map = { - comp.identifier: comp - for comp in self.design_spec.prompts - } - - def _process_special_component(self, component: PromptComponent) -> Optional[Dict[str, Any]]: - """ - 处理特殊组件(marker为True的组件) - - 参数: - component: 要处理的组件 - - 返回: - Optional[Dict[str, Any]]: 处理后的消息,如果组件无法处理则返回None - """ - try: - # 尝试将标识符转换为枚举 - special_id = SpecialIdentifier(component.identifier) - - # 根据不同标识符执行不同处理逻辑 - if special_id == SpecialIdentifier.CHAT_HISTORY: - return self._handle_chat_history(component) - elif special_id == SpecialIdentifier.WORLD_INFO_BEFORE: - return self._handle_world_info_before(component) - elif special_id == SpecialIdentifier.WORLD_INFO_AFTER: - return self._handle_world_info_after(component) - elif special_id == SpecialIdentifier.CHAR_DESCRIPTION: - return self._handle_char_description(component) - else: - # 未知特殊组件,使用默认处理 - return self._process_regular_component(component) - except ValueError: - # 不是特殊标识符,使用默认处理 - return self._process_regular_component(component) - - def _process_special_component(self, component: PromptComponent) -> Optional[Dict[str, Any]]: - """ - 处理特殊组件(marker为True的组件) - - 参数: - component: 要处理的组件 - - 返回: - Optional[Dict[str, Any]]: 处理后的消息,如果组件无法处理则返回None - """ - try: - # 尝试将标识符转换为枚举 - special_id = SpecialIdentifier(component.identifier) - - # 根据不同标识符执行不同处理逻辑 - if special_id == SpecialIdentifier.CHAT_HISTORY: - return self._handle_chat_history(component) - elif special_id == SpecialIdentifier.WORLD_INFO_BEFORE: - return self._handle_world_info_before(component) - elif special_id == SpecialIdentifier.WORLD_INFO_AFTER: - return self._handle_world_info_after(component) - elif special_id == SpecialIdentifier.DIALOGUE_EXAMPLES: - return self._handle_dialogue_examples(component) - elif special_id == SpecialIdentifier.CHAR_DESCRIPTION: - return self._handle_char_description(component) - elif special_id == SpecialIdentifier.CHAR_PERSONALITY: - return self._handle_char_personality(component) - elif special_id == SpecialIdentifier.SCENARIO: - return self._handle_scenario(component) - elif special_id == SpecialIdentifier.PERSONA_DESCRIPTION: - return self._handle_persona_description(component) - else: - # 未知特殊组件,使用默认处理 - return self._process_regular_component(component) - except ValueError: - # 不是特殊标识符,使用默认处理 - return self._process_regular_component(component) - - def _process_regular_component(self, component: PromptComponent) -> Dict[str, Any]: - """ - 处理普通组件(marker为False的组件) - - 参数: - component: 要处理的组件 - - 返回: - Dict[str, Any]: 处理后的消息 - """ - # 角色映射表 - role_map = {0: "system", 1: "user", 2: "assistant"} - - # 构建消息 - message = { - "role": role_map.get(component.role, "system"), - "content": component.content - } - - # 添加系统提示词标记 - if component.system_prompt: - message["system_prompt"] = True - - return message - - # 以下为特殊组件处理方法 - - def _handle_chat_history(self, component: PromptComponent) -> Dict[str, Any]: - """ - 处理聊天历史组件 - - 参数: - component: 聊天历史组件 - - 返回: - Dict[str, Any]: 处理后的消息 - """ - # 这里应该从外部获取实际的聊天历史 - # 示例实现,实际需要根据业务逻辑调整 - return { - "role": "system", - "content": "聊天历史内容...", - "marker": True, - "type": "chat_history" - } - - def _handle_world_info_before(self, component: PromptComponent) -> Dict[str, Any]: - """ - 处理前置世界信息组件 - - 参数: - component: 世界信息组件 - - 返回: - Dict[str, Any]: 处理后的消息 - """ - # 这里应该从外部获取实际的世界信息 - return { - "role": "system", - "content": "前置世界信息...", - "marker": True, - "type": "world_info_before" - } - - def _handle_world_info_after(self, component: PromptComponent) -> Dict[str, Any]: - """ - 处理后置世界信息组件 - - 参数: - component: 世界信息组件 - - 返回: - Dict[str, Any]: 处理后的消息 - """ - # 这里应该从外部获取实际的世界信息 - return { - "role": "system", - "content": "后置世界信息...", - "marker": True, - "type": "world_info_after" - } - - - def _handle_char_description(self, component: PromptComponent) -> Dict[str, Any]: - """ - 处理角色描述组件 - - 参数: - component: 角色描述组件 - - 返回: - Dict[str, Any]: 处理后的消息 - """ - # 这里应该从外部获取实际的角色描述 - return { - "role": "system", - "content": "角色描述内容...", - "marker": True, - "type": "char_description" - } - diff --git a/backend/nodes/__init__.py b/backend/nodes/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/nodes/text_splitter_node.py b/backend/nodes/text_splitter_node.py deleted file mode 100644 index 85ecd51..0000000 --- a/backend/nodes/text_splitter_node.py +++ /dev/null @@ -1,56 +0,0 @@ -import re -from core.node_base import BaseNode -from typing import List, Dict, Any - - -class TextSplitterNode(BaseNode): - name = "文本分割节点" - inputs = {"text": "string"} - outputs = { - "outline": "list", # 大纲部分列表 - "requirement": "list", # 要求部分列表 - "dialogue": "list", # 对话部分列表 - "weak_guidance": "list" # 弱指引部分列表 - } - - async def run(self, text: str) -> Dict[str, List[str]]: - # 正则匹配三种括号内的内容 - # 注意:此正则假设括号不嵌套,且没有转义字符 - pattern = r'\{([^{}]*)\}|\(([^()]*)\)|“([^”]*)”' - - outline = [] - requirement = [] - dialogue = [] - weak_guidance = [] - - pos = 0 - for match in re.finditer(pattern, text): - start, end = match.span() - # 处理匹配前的普通文本(弱指引) - if start > pos: - weak_part = text[pos:start].strip() - if weak_part: - weak_guidance.append(weak_part) - - # 根据捕获组确定类型 - if match.group(1) is not None: # 大括号 - outline.append(match.group(1).strip()) - elif match.group(2) is not None: # 小括号 - requirement.append(match.group(2).strip()) - elif match.group(3) is not None: # 中文引号 - dialogue.append(match.group(3).strip()) - - pos = end - - # 处理剩余的普通文本 - if pos < len(text): - weak_part = text[pos:].strip() - if weak_part: - weak_guidance.append(weak_part) - - return { - "outline": outline, - "requirement": requirement, - "dialogue": dialogue, - "weak_guidance": weak_guidance - } \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt index f8050aa..e7ce448 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,3 +1,10 @@ fastapi==0.104.1 uvicorn[standard]==0.24.0 -python-multipart==0.0.6 \ No newline at end of file +python-multipart==0.0.6 + +# LangChain for LLM integration (让 pip 自动解析兼容版本) +langchain>=0.1.0 +langchain-openai>=0.0.5 +langchain-anthropic>=0.1.1 +openai>=1.12.0 +anthropic>=0.23.0 \ No newline at end of file diff --git a/backend/tools/__init__.py b/backend/tools/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/tools/get_all_role_and_chat.py b/backend/tools/get_all_role_and_chat.py deleted file mode 100644 index 5fc805d..0000000 --- a/backend/tools/get_all_role_and_chat.py +++ /dev/null @@ -1,47 +0,0 @@ -from ..core import config -from typing import Dict, List - -# 使用配置中的 DATA_PATH 并添加 "chat" 子目录 -ROOT_DIR = config.settings.DATA_PATH / "chat" - -def get_all_role_and_chat() -> Dict[str, List[str]]: - """ - 读取配置目录下的所有子文件夹,并收集每个子文件夹中的 JSONL 文件 - - 返回: - dict: 字典结构,键是文件夹名称,值是该文件夹中的 JSONL 文件列表(仅文件名,无路径和后缀) - """ - result = {} - - # 确保目标目录存在 - if not ROOT_DIR.exists(): - print(f"警告: 目录 {ROOT_DIR} 不存在") - return result - - # 打印根目录路径和内容(调试用) - print(f"正在扫描目录: {ROOT_DIR}") - print(f"根目录内容: {list(ROOT_DIR.iterdir())}") - - # 遍历根目录下的所有条目 - for entry in ROOT_DIR.iterdir(): - try: - # 只处理文件夹 - if entry.is_dir(): - print(f"处理文件夹: {entry.name}") # 调试信息 - jsonl_files = [] - - # 遍历子文件夹中的所有文件 - for file in entry.iterdir(): - if file.is_file() and file.suffix == '.jsonl': - # 使用 file.stem 获取不带后缀的文件名 - jsonl_files.append(file.stem) - print(f" 找到文件: {file.name}") # 调试信息 - - # 如果该文件夹中有 JSONL 文件,则添加到结果中 - if jsonl_files: - result[entry.name] = jsonl_files - except Exception as e: - print(f"处理文件夹 {entry.name} 时出错: {str(e)}") - continue - - return result \ No newline at end of file diff --git a/backend/tools/save_input_to_json.py b/backend/tools/save_input_to_json.py deleted file mode 100644 index b79cdfe..0000000 --- a/backend/tools/save_input_to_json.py +++ /dev/null @@ -1,152 +0,0 @@ -import json -from datetime import datetime -from backend.core import config as cfg -from pathlib import Path -from ..core.items import ChatRequest - -# 假设 ChatRequest 定义在这里或者从其他地方导入 -# from backend.app.core.items import ChatRequest - -async def save_input_to_json(chat_request: ChatRequest): - """ - 保存消息到JSONL文件或处理重roll请求 - - 参数: - chat_request: 包含消息详情的请求对象 - """ - # 1. 从对象中提取属性 - mes = chat_request.mes - role_name = chat_request.role_name - chat_name = chat_request.chat_name - name = chat_request.name - is_user = chat_request.is_user - floor_number = chat_request.floor_number - # stream, img_switch, table_switch 等虽然在这个函数逻辑中没用到, - # 但如果 ChatRequest 中有,也可以提取出来备用 - # stream = chat_request.stream - # ... - - config = cfg.settings - # 注意:这里要确保 role_name 和 chat_name 不为 None,否则路径拼接会报错 - # 建议在函数入口处增加校验,或者在 Pydantic 模型中设置为必填项 - if not role_name or not chat_name: - raise ValueError("role_name and chat_name cannot be empty") - - file_path = config.BASE_PATH / "data" / "chat" / role_name / f"{chat_name}.jsonl" - - # 确保目录存在 - Path(file_path).parent.mkdir(parents=True, exist_ok=True) - - # 读取文件内容 - try: - with open(file_path, 'r', encoding='utf-8') as f: - lines = f.readlines() - except FileNotFoundError: - lines = [] - - # 判断是否为重roll请求 - is_regenerate = False - target_index = -1 - - if lines and floor_number > 0: - # 计算当前楼层号 - current_floor = len(lines) - - # 如果floor_number与当前楼层号相同,则为重roll请求 - if floor_number == current_floor: - # 找到最后一条非用户消息 - for i in range(len(lines) - 1, -1, -1): - try: - line_data = json.loads(lines[i]) - if not line_data.get('is_user', False): - is_regenerate = True - target_index = i - break - except json.JSONDecodeError: - continue - - # 处理重roll逻辑 - if is_regenerate: - # 解析目标消息 - try: - target_message = json.loads(lines[target_index]) - except json.JSONDecodeError: - raise ValueError(f"无法解析楼层 {floor_number} 的JSON数据") - - # 初始化swipes数组 - if target_message.get('swipes') is None: - target_message['swipes'] = [] - - # 将新回复添加到swipes数组 - target_message['swipes'].append(mes) - - # 更新swipe_id和content - target_message['swipes_id'] = len(target_message['swipes']) - 1 - target_message['content'] = mes - - # 更新文件内容 - lines[target_index] = json.dumps(target_message, ensure_ascii=False) + '\n' - - # 写回文件 - with open(file_path, 'w', encoding='utf-8') as f: - f.writelines(lines) - - return target_message - - # 处理普通消息保存逻辑 - else: - # 获取当前时间 - current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # 构建消息对象 - message = { - "role": role_name, - "chat": chat_name, - "content": mes, - "name": name, - "is_user": is_user, - "send_date": current_time, - "floor_number": len(lines) + 1, # 记录楼层号 - "swipes": [], - "swipes_id": 0 - } - - # 追加到文件 - with open(file_path, 'a', encoding='utf-8') as f: - f.write(json.dumps(message, ensure_ascii=False) + '\n') - - return message - - -if __name__ == '__main__': - # 注意:为了在本地运行测试,你需要手动构造一个 ChatRequest 对象 - # 或者临时修改函数签名以便直接传参测试 - - # 示例:假设 ChatRequest 是一个简单的类或 Pydantic 模型 - class MockChatRequest: - def __init__(self, **kwargs): - self.mes = kwargs.get('mes') - self.role_name = kwargs.get('role_name') - self.chat_name = kwargs.get('chat_name') - self.name = kwargs.get('name') - self.is_user = kwargs.get('is_user') - self.floor_number = kwargs.get('floor_number') - - - # 测试重roll最后一条AI消息 - import asyncio - - - async def test(): - req = MockChatRequest( - mes="这是重roll后的新回复2", - role_name="testRole1", - chat_name="111", - name="AI", - is_user=False, - floor_number=2 - ) - await save_input_to_json(req) - - - asyncio.run(test()) diff --git a/backend/workflows/__init__.py b/backend/workflows/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/workflows/main_llm_workflow.py b/backend/workflows/main_llm_workflow.py deleted file mode 100644 index 238852c..0000000 --- a/backend/workflows/main_llm_workflow.py +++ /dev/null @@ -1,201 +0,0 @@ -# backend/app/workflows/llm_workflow.py -from typing import Dict, Any, List, Callable -from dataclasses import dataclass -from enum import Enum - - -class WorkflowStatus(Enum): - """工作流状态枚举""" - INITIALIZED = "initialized" - RUNNING = "running" - COMPLETED = "completed" - FAILED = "failed" - PAUSED = "paused" - - -@dataclass -class WorkflowContext: - """工作流上下文""" - data: Dict[str, Any] - status: WorkflowStatus = WorkflowStatus.INITIALIZED - metadata: Dict[str, Any] = None - - def __post_init__(self): - if self.metadata is None: - self.metadata = {} - - -class WorkflowNode: - """工作流节点声明""" - - def __init__( - self, - name: str, - handler: Callable, - enabled: bool = True, - config: Dict[str, Any] = None - ): - self.name = name # 节点的唯一标识符,用于区分不同的节点。 - self.handler = handler # 一个可调用对象(函数或方法),这是节点实际执行的处理逻辑。 - self.enabled = enabled # 布尔值,控制节点是否启用。默认为 True,如果设置为 False,节点将被跳过。 - self.config = config or {} # 一个字典,用于存储节点的配置信息。默认为空字典。 - self.next_nodes: List['WorkflowNode'] = [] # 一个节点列表,用于指定当前节点执行完成后应跳转到的下一个节点。默认为空列表,可能指向多分支。 - - def execute(self, context: WorkflowContext) -> WorkflowContext: - """执行节点处理""" - if not self.enabled: - return context - - try: - context = self.handler(context, self.config) - return context - except Exception as e: - context.status = WorkflowStatus.FAILED - context.metadata["error"] = str(e) - raise - - -class LLMWorkflow: - """LLM工作流声明""" - - def __init__(self): - self.nodes: List[WorkflowNode] = [] - self._initialize_workflow() - - def _initialize_workflow(self): - """初始化工作流节点(仅声明,不实现)""" - # 输入节点 - input_node = WorkflowNode( - name="input", - handler=self._input_handler - ) - - # 输入预处理节点(可开关) - preprocessing_node = WorkflowNode( - name="preprocessing", - handler=self._preprocessing_handler, - enabled=False - ) - - # RAG处理节点 - rag_node = WorkflowNode( - name="rag", - handler=self._rag_handler - ) - - # 提示词组装节点 - prompt_assembly_node = WorkflowNode( - name="prompt_assembly", - handler=self._prompt_assembly_handler - ) - - # LLM请求节点 - llm_request_node = WorkflowNode( - name="llm_request", - handler=self._llm_request_handler - ) - - # 图像生成节点(可开关) - image_generation_node = WorkflowNode( - name="image_generation", - handler=self._image_generation_handler, - enabled=False - ) - - # 动态表格更新节点(可开关) - dynamic_table_node = WorkflowNode( - name="dynamic_table", - handler=self._dynamic_table_handler, - enabled=False - ) - - # 输出过滤节点 - output_filter_node = WorkflowNode( - name="output_filter", - handler=self._output_filter_handler - ) - - # 输出节点 - output_node = WorkflowNode( - name="output", - handler=self._output_handler - ) - - # 设置节点顺序(构建工作流) - self.nodes = [ - input_node, - preprocessing_node, - rag_node, - prompt_assembly_node, - llm_request_node, - image_generation_node, - dynamic_table_node, - output_filter_node, - output_node - ] - - def execute(self, context: WorkflowContext) -> WorkflowContext: - """执行工作流""" - context.status = WorkflowStatus.RUNNING - - for node in self.nodes: - try: - context = node.execute(context) - - # 如果工作流失败,停止执行 - if context.status == WorkflowStatus.FAILED: - break - except Exception as e: - context.status = WorkflowStatus.FAILED - context.metadata["error"] = str(e) - break - - if context.status != WorkflowStatus.FAILED: - context.status = WorkflowStatus.COMPLETED - - return context - - def enable_node(self, node_name: str, enabled: bool = True): - """启用或禁用特定节点""" - for node in self.nodes: - if node.name == node_name: - node.enabled = enabled - return True - return False - - # 以下是节点处理函数声明(仅声明,不实现) - def _input_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext: - """输入节点处理函数""" - pass - - def _preprocessing_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext: - """输入预处理节点处理函数""" - pass - - def _rag_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext: - """RAG处理节点处理函数""" - pass - - def _prompt_assembly_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext: - """提示词组装节点处理函数""" - pass - - def _llm_request_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext: - """LLM请求节点处理函数""" - pass - - def _image_generation_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext: - """图像生成节点处理函数""" - pass - - def _dynamic_table_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext: - """动态表格更新节点处理函数""" - pass - - def _output_filter_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext: - """输出过滤节点处理函数""" - pass - - def _output_handler(self, context: WorkflowContext, config: Dict[str, Any]) -> WorkflowContext: - """输出节点处理函数""" - pass diff --git a/docker-compose.yml b/docker-compose.yml index 8971e32..c94e46b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,33 +2,54 @@ version: '3.8' services: backend: - build: ./backend - command: uvicorn backend.main:app --host 0.0.0.0 --port 8000 --reload - ports: - - "23337:8000" + build: + context: ./backend + dockerfile: Dockerfile + container_name: llm-backend + command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload volumes: - - ./backend:/app/backend + - ./backend:/app - ./data:/app/data - - ./outputs:/outputs + - ./outputs:/app/outputs environment: - PYTHONUNBUFFERED=1 + - PYTHONDONTWRITEBYTECODE=1 restart: unless-stopped + networks: + - llm-network + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s frontend: build: - context: ./frontend-react + context: ./frontend dockerfile: Dockerfile + target: development + container_name: llm-frontend ports: - "23338:5173" volumes: - - ./frontend-react:/app + - ./frontend:/app - /app/node_modules environment: - # 如果不需要特定环境变量,可以完全移除 environment 部分 - # 或者添加有效的环境变量,例如: - NODE_ENV=development - - VITE_BACKEND_URL=http://backend:8000 + - VITE_API_URL=http://backend:8000 + - VITE_WS_URL=ws://backend:8000 command: sh -c "npm install && npm run dev -- --host 0.0.0.0" depends_on: - - backend + backend: + condition: service_healthy restart: unless-stopped + networks: + - llm-network + +networks: + llm-network: + driver: bridge + +volumes: + node_modules: diff --git a/frontend-react/Dockerfile b/frontend-react/Dockerfile deleted file mode 100644 index 2402881..0000000 --- a/frontend-react/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# 使用 Node.js 18 Alpine 镜像作为基础 -# 必须明确指定 node 版本,否则可能默认为空或 python 镜像 -FROM node:20-alpine - -# 设置工作目录 -WORKDIR /app - -# 设置 npm 镜像源(可选,国内推荐使用,加速依赖下载) - RUN npm config set registry https://registry.npmmirror.com/ - -# 复制 package.json 和 package-lock.json -# 利用 Docker 缓存层,只有依赖变更时才重新安装 -COPY package.json package-lock.json* ./ - -# 安装依赖 -RUN npm install - -# 复制源代码到容器 -COPY . . - -# 暴露 Vite 默认端口 5173 -EXPOSE 5173 - -# 启动 Vite 开发服务器 -# --host 0.0.0.0 允许外部访问(Docker 容器外) -CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"] diff --git a/frontend-react/index.html b/frontend-react/index.html deleted file mode 100644 index 1a32246..0000000 --- a/frontend-react/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - React App - - - -
- - - - diff --git a/frontend-react/package-lock.json b/frontend-react/package-lock.json deleted file mode 100644 index 76a4ad5..0000000 --- a/frontend-react/package-lock.json +++ /dev/null @@ -1,4667 +0,0 @@ -{ - "name": "ai-chat-frontend", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "ai-chat-frontend", - "version": "0.0.0", - "dependencies": { - "highlight.js": "^11.11.1", - "katex": "^0.16.38", - "marked": "^17.0.4", - "marked-highlight": "^2.2.3", - "mermaid": "^11.13.0", - "react": "^18.3.1", - "react-copy-to-clipboard": "^5.1.1", - "react-dom": "^18.3.1", - "react-markdown": "^10.1.0", - "react-syntax-highlighter": "^16.1.1", - "rehype-katex": "^7.0.1", - "remark-math": "^6.0.0", - "zustand": "^4.4.7" - }, - "devDependencies": { - "@types/react": "^18.2.43", - "@types/react-dom": "^18.2.17", - "@vitejs/plugin-react": "^4.2.1", - "vite": "^5.0.8" - } - }, - "node_modules/@antfu/install-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/@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/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@braintree/sanitize-url": { - "version": "7.1.2", - "resolved": "https://registry.npmmirror.com/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", - "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", - "license": "MIT" - }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "11.1.2", - "resolved": "https://registry.npmmirror.com/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.2.tgz", - "integrity": "sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "11.1.2", - "@chevrotain/types": "11.1.2", - "lodash-es": "4.17.23" - } - }, - "node_modules/@chevrotain/gast": { - "version": "11.1.2", - "resolved": "https://registry.npmmirror.com/@chevrotain/gast/-/gast-11.1.2.tgz", - "integrity": "sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "11.1.2", - "lodash-es": "4.17.23" - } - }, - "node_modules/@chevrotain/regexp-to-ast": { - "version": "11.1.2", - "resolved": "https://registry.npmmirror.com/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.2.tgz", - "integrity": "sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/types": { - "version": "11.1.2", - "resolved": "https://registry.npmmirror.com/@chevrotain/types/-/types-11.1.2.tgz", - "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/utils": { - "version": "11.1.2", - "resolved": "https://registry.npmmirror.com/@chevrotain/utils/-/utils-11.1.2.tgz", - "integrity": "sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==", - "license": "Apache-2.0" - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@iconify/types": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "license": "MIT" - }, - "node_modules/@iconify/utils": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/@iconify/utils/-/utils-3.1.0.tgz", - "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", - "license": "MIT", - "dependencies": { - "@antfu/install-pkg": "^1.1.0", - "@iconify/types": "^2.0.0", - "mlly": "^1.8.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@mermaid-js/parser": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/@mermaid-js/parser/-/parser-1.0.1.tgz", - "integrity": "sha512-opmV19kN1JsK0T6HhhokHpcVkqKpF+x2pPDKKM2ThHtZAB5F4PROopk0amuVYK5qMrIA4erzpNm8gmPNJgMDxQ==", - "license": "MIT", - "dependencies": { - "langium": "^4.0.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/d3": { - "version": "7.4.3", - "resolved": "https://registry.npmmirror.com/@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.2", - "resolved": "https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "license": "MIT" - }, - "node_modules/@types/d3-axis": { - "version": "3.0.6", - "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", - "license": "MIT" - }, - "node_modules/@types/d3-dispatch": { - "version": "3.0.7", - "resolved": "https://registry.npmmirror.com/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", - "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", - "license": "MIT" - }, - "node_modules/@types/d3-drag": { - "version": "3.0.7", - "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.8", - "resolved": "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.8.tgz", - "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", - "license": "MIT", - "dependencies": { - "@types/d3-path": "*" - } - }, - "node_modules/@types/d3-time": { - "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", - "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/katex": { - "version": "0.16.8", - "resolved": "https://registry.npmmirror.com/@types/katex/-/katex-0.16.8.tgz", - "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", - "license": "MIT" - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/prismjs": { - "version": "1.26.6", - "resolved": "https://registry.npmmirror.com/@types/prismjs/-/prismjs-1.26.6.tgz", - "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.28", - "resolved": "https://registry.npmmirror.com/@types/react/-/react-18.3.28.tgz", - "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^18.0.0" - } - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT", - "optional": true - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "license": "ISC" - }, - "node_modules/@upsetjs/venn.js": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", - "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", - "license": "MIT", - "optionalDependencies": { - "d3-selection": "^3.0.0", - "d3-transition": "^3.0.1" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.8", - "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", - "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001780", - "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", - "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chevrotain": { - "version": "11.1.2", - "resolved": "https://registry.npmmirror.com/chevrotain/-/chevrotain-11.1.2.tgz", - "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/cst-dts-gen": "11.1.2", - "@chevrotain/gast": "11.1.2", - "@chevrotain/regexp-to-ast": "11.1.2", - "@chevrotain/types": "11.1.2", - "@chevrotain/utils": "11.1.2", - "lodash-es": "4.17.23" - } - }, - "node_modules/chevrotain-allstar": { - "version": "0.3.1", - "resolved": "https://registry.npmmirror.com/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/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/copy-to-clipboard": { - "version": "3.3.3", - "resolved": "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", - "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", - "license": "MIT", - "dependencies": { - "toggle-selection": "^1.0.6" - } - }, - "node_modules/cose-base": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/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.2.3", - "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/cytoscape": { - "version": "3.33.1", - "resolved": "https://registry.npmmirror.com/cytoscape/-/cytoscape-3.33.1.tgz", - "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/cytoscape-cose-bilkent": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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-dsv/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.2", - "resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.2.tgz", - "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-geo": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.14", - "resolved": "https://registry.npmmirror.com/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", - "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", - "license": "MIT", - "dependencies": { - "d3": "^7.9.0", - "lodash-es": "^4.17.21" - } - }, - "node_modules/dayjs": { - "version": "1.11.20", - "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz", - "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", - "license": "ISC", - "dependencies": { - "robust-predicates": "^3.0.2" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/dompurify": { - "version": "3.3.3", - "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.3.3.tgz", - "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.321", - "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", - "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/estree-util-is-identifier-name": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", - "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/fault": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/fault/-/fault-1.0.4.tgz", - "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", - "license": "MIT", - "dependencies": { - "format": "^0.2.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/format": { - "version": "0.2.2", - "resolved": "https://registry.npmmirror.com/format/-/format-0.2.2.tgz", - "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/hachure-fill": { - "version": "0.5.2", - "resolved": "https://registry.npmmirror.com/hachure-fill/-/hachure-fill-0.5.2.tgz", - "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", - "license": "MIT" - }, - "node_modules/hast-util-from-dom": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", - "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", - "license": "ISC", - "dependencies": { - "@types/hast": "^3.0.0", - "hastscript": "^9.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-html": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", - "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "devlop": "^1.1.0", - "hast-util-from-parse5": "^8.0.0", - "parse5": "^7.0.0", - "vfile": "^6.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-html-isomorphic": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", - "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-from-dom": "^5.0.0", - "hast-util-from-html": "^2.0.0", - "unist-util-remove-position": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-parse5": { - "version": "8.0.3", - "resolved": "https://registry.npmmirror.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", - "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "hastscript": "^9.0.0", - "property-information": "^7.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-is-element": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", - "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-parse-selector": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", - "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.6", - "resolved": "https://registry.npmmirror.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", - "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-js": "^1.0.0", - "unist-util-position": "^5.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-text": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", - "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "hast-util-is-element": "^3.0.0", - "unist-util-find-after": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "9.0.1", - "resolved": "https://registry.npmmirror.com/hastscript/-/hastscript-9.0.1.tgz", - "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/highlight.js": { - "version": "11.11.1", - "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.11.1.tgz", - "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/highlightjs-vue": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", - "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", - "license": "CC0-1.0" - }, - "node_modules/html-url-attributes": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz", - "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmmirror.com/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/inline-style-parser": { - "version": "0.2.7", - "resolved": "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz", - "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", - "license": "MIT" - }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/is-alphabetical": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-decimal": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-hexadecimal": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", - "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/katex": { - "version": "0.16.38", - "resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.38.tgz", - "integrity": "sha512-cjHooZUmIAUmDsHBN+1n8LaZdpmbj03LtYeYPyuYB7OuloiaeaV6N4LcfjcnHVzGWjVQmKrxxTrpDcmSzEZQwQ==", - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" - ], - "license": "MIT", - "dependencies": { - "commander": "^8.3.0" - }, - "bin": { - "katex": "cli.js" - } - }, - "node_modules/khroma": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/khroma/-/khroma-2.1.0.tgz", - "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" - }, - "node_modules/langium": { - "version": "4.2.1", - "resolved": "https://registry.npmmirror.com/langium/-/langium-4.2.1.tgz", - "integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==", - "license": "MIT", - "dependencies": { - "chevrotain": "~11.1.1", - "chevrotain-allstar": "~0.3.1", - "vscode-languageserver": "~9.0.1", - "vscode-languageserver-textdocument": "~1.0.11", - "vscode-uri": "~3.1.0" - }, - "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" - } - }, - "node_modules/layout-base": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/layout-base/-/layout-base-1.0.2.tgz", - "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", - "license": "MIT" - }, - "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", - "license": "MIT" - }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lowlight": { - "version": "1.20.0", - "resolved": "https://registry.npmmirror.com/lowlight/-/lowlight-1.20.0.tgz", - "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", - "license": "MIT", - "dependencies": { - "fault": "^1.0.0", - "highlight.js": "~10.7.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/lowlight/node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/marked": { - "version": "17.0.4", - "resolved": "https://registry.npmmirror.com/marked/-/marked-17.0.4.tgz", - "integrity": "sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/marked-highlight": { - "version": "2.2.3", - "resolved": "https://registry.npmmirror.com/marked-highlight/-/marked-highlight-2.2.3.tgz", - "integrity": "sha512-FCfZRxW/msZAiasCML4isYpxyQWKEEx44vOgdn5Kloae+Qc3q4XR7WjpKKf8oMLk7JP9ZCRd2vhtclJFdwxlWQ==", - "license": "MIT", - "peerDependencies": { - "marked": ">=4 <18" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", - "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-math": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/mdast-util-math/-/mdast-util-math-3.0.0.tgz", - "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "longest-streak": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.1.0", - "unist-util-remove-position": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-expression": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", - "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-jsx": { - "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", - "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdxjs-esm": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", - "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "resolved": "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mermaid": { - "version": "11.13.0", - "resolved": "https://registry.npmmirror.com/mermaid/-/mermaid-11.13.0.tgz", - "integrity": "sha512-fEnci+Immw6lKMFI8sqzjlATTyjLkRa6axrEgLV2yHTfv8r+h1wjFbV6xeRtd4rUV1cS4EpR9rwp3Rci7TRWDw==", - "license": "MIT", - "dependencies": { - "@braintree/sanitize-url": "^7.1.1", - "@iconify/utils": "^3.0.2", - "@mermaid-js/parser": "^1.0.1", - "@types/d3": "^7.4.3", - "@upsetjs/venn.js": "^2.0.0", - "cytoscape": "^3.33.1", - "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.14", - "dayjs": "^1.11.19", - "dompurify": "^3.3.1", - "katex": "^0.16.25", - "khroma": "^2.1.0", - "lodash-es": "^4.17.23", - "marked": "^16.3.0", - "roughjs": "^4.6.6", - "stylis": "^4.3.6", - "ts-dedent": "^2.2.0", - "uuid": "^11.1.0" - } - }, - "node_modules/mermaid/node_modules/marked": { - "version": "16.4.2", - "resolved": "https://registry.npmmirror.com/marked/-/marked-16.4.2.tgz", - "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-math": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", - "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", - "license": "MIT", - "dependencies": { - "@types/katex": "^0.16.0", - "devlop": "^1.0.0", - "katex": "^0.16.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/mlly": { - "version": "1.8.1", - "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.1.tgz", - "integrity": "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==", - "license": "MIT", - "dependencies": { - "acorn": "^8.16.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.3" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/package-manager-detector": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/package-manager-detector/-/package-manager-detector-1.6.0.tgz", - "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", - "license": "MIT" - }, - "node_modules/parse-entities": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/parse-entities/-/parse-entities-4.0.2.tgz", - "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmmirror.com/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/path-data-parser": { - "version": "0.1.0", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/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/points-on-curve": { - "version": "0.2.0", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.5.8", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prismjs": { - "version": "1.30.0", - "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz", - "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmmirror.com/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-copy-to-clipboard": { - "version": "5.1.1", - "resolved": "https://registry.npmmirror.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.1.tgz", - "integrity": "sha512-s+HrzLyJBxrpGTYXF15dTgMjAJpEPZT/Yp6NytAtZMRngejxt6Pt5WrfFxLAcsqUDU6sY1Jz6tyHwIicE1U2Xg==", - "license": "MIT", - "dependencies": { - "copy-to-clipboard": "^3.3.3", - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "react": ">=15.3.0" - } - }, - "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/react-markdown": { - "version": "10.1.0", - "resolved": "https://registry.npmmirror.com/react-markdown/-/react-markdown-10.1.0.tgz", - "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "hast-util-to-jsx-runtime": "^2.0.0", - "html-url-attributes": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.0.0", - "unified": "^11.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "@types/react": ">=18", - "react": ">=18" - } - }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-syntax-highlighter": { - "version": "16.1.1", - "resolved": "https://registry.npmmirror.com/react-syntax-highlighter/-/react-syntax-highlighter-16.1.1.tgz", - "integrity": "sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.28.4", - "highlight.js": "^10.4.1", - "highlightjs-vue": "^1.0.0", - "lowlight": "^1.17.0", - "prismjs": "^1.30.0", - "refractor": "^5.0.0" - }, - "engines": { - "node": ">= 16.20.2" - }, - "peerDependencies": { - "react": ">= 0.14.0" - } - }, - "node_modules/react-syntax-highlighter/node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, - "node_modules/refractor": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/refractor/-/refractor-5.0.0.tgz", - "integrity": "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/prismjs": "^1.0.0", - "hastscript": "^9.0.0", - "parse-entities": "^4.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/rehype-katex": { - "version": "7.0.1", - "resolved": "https://registry.npmmirror.com/rehype-katex/-/rehype-katex-7.0.1.tgz", - "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/katex": "^0.16.0", - "hast-util-from-html-isomorphic": "^2.0.0", - "hast-util-to-text": "^4.0.0", - "katex": "^0.16.0", - "unist-util-visit-parents": "^6.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-math": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/remark-math/-/remark-math-6.0.0.tgz", - "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-math": "^3.0.0", - "micromark-extension-math": "^3.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-parse": { - "version": "11.0.0", - "resolved": "https://registry.npmmirror.com/remark-parse/-/remark-parse-11.0.0.tgz", - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-rehype": { - "version": "11.1.2", - "resolved": "https://registry.npmmirror.com/remark-rehype/-/remark-rehype-11.1.2.tgz", - "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "mdast-util-to-hast": "^13.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", - "license": "Unlicense" - }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/roughjs": { - "version": "4.6.6", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmmirror.com/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/style-to-js": { - "version": "1.1.21", - "resolved": "https://registry.npmmirror.com/style-to-js/-/style-to-js-1.1.21.tgz", - "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", - "license": "MIT", - "dependencies": { - "style-to-object": "1.0.14" - } - }, - "node_modules/style-to-object": { - "version": "1.0.14", - "resolved": "https://registry.npmmirror.com/style-to-object/-/style-to-object-1.0.14.tgz", - "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", - "license": "MIT", - "dependencies": { - "inline-style-parser": "0.2.7" - } - }, - "node_modules/stylis": { - "version": "4.3.6", - "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz", - "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.0.4.tgz", - "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/toggle-selection": { - "version": "1.0.6", - "resolved": "https://registry.npmmirror.com/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", - "license": "MIT" - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/ts-dedent": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/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.3", - "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "license": "MIT" - }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmmirror.com/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-find-after": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", - "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-is": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-remove-position": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", - "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmmirror.com/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/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-location": { - "version": "5.0.3", - "resolved": "https://registry.npmmirror.com/vfile-location/-/vfile-location-5.0.3.tgz", - "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", - "license": "MIT" - }, - "node_modules/vscode-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "license": "MIT" - }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/zustand": { - "version": "4.5.7", - "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.7.tgz", - "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", - "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.2.2" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } - } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} diff --git a/frontend-react/package.json b/frontend-react/package.json deleted file mode 100644 index 2119ef1..0000000 --- a/frontend-react/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "ai-chat-frontend", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "highlight.js": "^11.11.1", - "katex": "^0.16.38", - "marked": "^17.0.4", - "marked-highlight": "^2.2.3", - "mermaid": "^11.13.0", - "react": "^18.3.1", - "react-copy-to-clipboard": "^5.1.1", - "react-dom": "^18.3.1", - "react-markdown": "^10.1.0", - "react-syntax-highlighter": "^16.1.1", - "rehype-katex": "^7.0.1", - "remark-math": "^6.0.0", - "zustand": "^4.4.7" - }, - "devDependencies": { - "@types/react": "^18.2.43", - "@types/react-dom": "^18.2.17", - "@vitejs/plugin-react": "^4.2.1", - "vite": "^5.0.8" - } -} diff --git a/frontend-react/src/App.jsx b/frontend-react/src/App.jsx deleted file mode 100644 index d4d7f64..0000000 --- a/frontend-react/src/App.jsx +++ /dev/null @@ -1,31 +0,0 @@ -// frontend-react/src/App.jsx -import React from 'react'; -import Toolbar from './components/ToolBar/ToolBar'; -import ChatBox from './components/ChatBox/ChatBox'; -import SideBarLeft from './components/SideBarLeft/SideBarLeft'; -import SideBarRight from './components/SideBarRight/SideBarRight'; -import './index.css'; - -function App() { - return ( -
- - - {/* 主内容容器 */} -
- {/* 左侧栏 - 预设面板 */} - - - {/* 中间栏:聊天框 */} -
- -
- - {/* 右侧栏 */} - -
-
- ); -} - -export default App; diff --git a/frontend-react/src/Store/Slices/ChatBoxSlice.jsx b/frontend-react/src/Store/Slices/ChatBoxSlice.jsx deleted file mode 100644 index c194623..0000000 --- a/frontend-react/src/Store/Slices/ChatBoxSlice.jsx +++ /dev/null @@ -1,275 +0,0 @@ -import { create } from 'zustand'; -import { subscribeWithSelector } from 'zustand/middleware'; - -const useChatBoxStore = create( - subscribeWithSelector((set, get) => ({ - // 聊天历史消息列表 - messages: [], - - // 用户名称 - userName: '', - - // 角色名称 - characterName: '', - - // 当前选中的角色 - currentRole: null, - - // 当前选中的聊天 - currentChat: null, - - // 是否正在加载 - isLoading: false, - - // 是否正在生成 - isGenerating: false, - - // 错误信息 - error: null, - - // 设置消息列表 - setMessages: (messages) => set({ messages }), - - // 设置用户名称 - setUserName: (userName) => set({ userName }), - - // 设置角色名称 - setCharacterName: (characterName) => set({ characterName }), - - // 设置当前角色 - setCurrentRole: (role) => set({ currentRole: role }), - - // 设置当前聊天 - setCurrentChat: (chat) => set({ currentChat: chat }), - - // 同时设置角色和聊天 - setChatBoxRoleAndChat: (role, chat) => { - console.log('[ChatBoxStore] setChatBoxRoleAndChat called with:', { role, chat }); - set({ - currentRole: role, - currentChat: chat - }); - }, - - // 设置生成状态 - setIsGenerating: (status) => set({ isGenerating: status }), - - // 发送消息 - sendMessage: async (content) => { - const { messages, userName, characterName, currentRole, currentChat } = get(); - - set({ - isGenerating: true, - messages: [...messages, { - id: Date.now(), - floor: messages.length + 1, - mes: content, - is_user: true - }] - }); - - try { - const response = await fetch(`/api/chats/${encodeURIComponent(currentRole)}/${encodeURIComponent(currentChat)}/messages`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - floor: messages.length + 1, - mes: content, - is_user: true - }) - }); - - if (!response.ok) { - throw new Error('Failed to send message'); - } - - const data = await response.json(); - set((state) => ({ - messages: [...state.messages, { - id: Date.now(), - floor: state.messages.length + 1, - mes: data.response, - is_user: false - }], - isGenerating: false - })); - } catch (error) { - set({ - error: error.message, - isGenerating: false - }); - } - }, - - // 终止生成 - stopGeneration: () => set({ isGenerating: false }), - - // 加载聊天历史 - fetchChatHistory: async (roleName, chatName) => { - set({ isLoading: true, error: null }); - try { - const response = await fetch(`/api/chats/${encodeURIComponent(roleName)}/${encodeURIComponent(chatName)}`); - if (!response.ok) { - throw new Error('Failed to fetch chat history'); - } - const data = await response.json(); - - set({ - messages: data.messages || [], - userName: data.metadata?.user_name || 'User', - characterName: data.metadata?.character_name || roleName || 'Assistant', - isLoading: false - }); - } catch (error) { - set({ - error: error.message, - isLoading: false - }); - } - }, - - // 清空聊天历史 - clearChatHistory: () => set({ - messages: [], - userName: '', - characterName: '', - error: null - }), - - // 更新特定消息的内容 - updateMessage: async (floor, content) => { - const { currentRole, currentChat } = get(); - try { - const response = await fetch(`/api/chats/${encodeURIComponent(currentRole)}/${encodeURIComponent(currentChat)}/messages/${floor}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ mes: content }) - }); - - if (!response.ok) { - throw new Error('Failed to update message'); - } - - set((state) => ({ - messages: state.messages.map((msg) => - msg.floor === floor ? { ...msg, mes: content } : msg - ) - })); - } catch (error) { - set({ error: error.message }); - } - }, - - // 删除特定消息 - deleteMessage: async (floor) => { - const { currentRole, currentChat } = get(); - try { - const response = await fetch(`/api/chats/${encodeURIComponent(currentRole)}/${encodeURIComponent(currentChat)}/messages/${floor}`, { - method: 'DELETE' - }); - - if (!response.ok) { - throw new Error('Failed to delete message'); - } - - set((state) => ({ - messages: state.messages.filter((msg) => msg.floor !== floor) - })); - } catch (error) { - set({ error: error.message }); - } - }, - - // 创建新聊天 - createChat: async (roleName, chatName, metadata = {}) => { - try { - const response = await fetch(`/api/chats/${encodeURIComponent(roleName)}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - chat_name: chatName, - metadata: { - user_name: 'User', - character_name: roleName, - ...metadata - } - }) - }); - - if (!response.ok) { - throw new Error('Failed to create chat'); - } - - return await response.json(); - } catch (error) { - set({ error: error.message }); - throw error; - } - }, - - // 更新聊天元数据 - updateChatMetadata: async (roleName, chatName, metadata) => { - try { - const response = await fetch(`/api/chats/${encodeURIComponent(roleName)}/${encodeURIComponent(chatName)}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ metadata }) - }); - - if (!response.ok) { - throw new Error('Failed to update chat metadata'); - } - - return await response.json(); - } catch (error) { - set({ error: error.message }); - throw error; - } - }, - - // 删除聊天 - deleteChat: async (roleName, chatName) => { - try { - const response = await fetch(`/api/chats/${encodeURIComponent(roleName)}/${encodeURIComponent(chatName)}`, { - method: 'DELETE' - }); - - if (!response.ok) { - throw new Error('Failed to delete chat'); - } - - return await response.json(); - } catch (error) { - set({ error: error.message }); - throw error; - } - } - })) -); - -// 监听角色和聊天变化,自动加载聊天历史 -useChatBoxStore.subscribe( - (state) => ({ role: state.currentRole, chat: state.currentChat }), - ({ role, chat }, prev) => { - // 只有当角色或聊天发生变化时才处理 - if (role !== prev.role || chat !== prev.chat) { - // 确保角色和聊天都存在且不为null - if (role && chat) { - useChatBoxStore.getState().fetchChatHistory(role, chat); - } else { - useChatBoxStore.getState().clearChatHistory(); - } - } - }, - { equalityFn: (a, b) => a.role === b.role && a.chat === b.chat } -); - -export default useChatBoxStore; diff --git a/frontend-react/src/Store/Slices/LeftTabsSlices/PresetSlice.jsx b/frontend-react/src/Store/Slices/LeftTabsSlices/PresetSlice.jsx deleted file mode 100644 index 826842a..0000000 --- a/frontend-react/src/Store/Slices/LeftTabsSlices/PresetSlice.jsx +++ /dev/null @@ -1,356 +0,0 @@ -import { create } from 'zustand'; - -const usePresetStore = create((set, get) => ({ - // 预设选择 - selectedPreset: '', - - // 核心参数 - parameters: { - temperature: 1.0, - frequency_penalty: 0.0, - presence_penalty: 0.0, - top_p: 1.0, - top_k: 0, - max_context: 1000000, - max_tokens: 30000, - max_context_unlocked: false, - stream_openai: true, - seed: -1, - n: 1 - }, - - // 可用的预设列表 - 初始为空,将从后端加载 - presets: [], - - // 是否正在加载预设列表 - isLoadingPresets: false, - - // 参数设置折叠状态 - isParametersExpanded: true, - - // 预设组件列表 - promptComponents: [ - { - identifier: "dialogueExamples", - name: "Chat Examples", - system_prompt: true, - marker: true, - enabled: true, - role: 0 - }, - { - identifier: "chatHistory", - name: "Chat History", - system_prompt: true, - marker: true, - enabled: true, - role: 0 - }, - { - identifier: "worldInfoAfter", - name: "World Info (after)", - system_prompt: true, - marker: true, - enabled: true, - role: 0 - }, - { - identifier: "worldInfoBefore", - name: "World Info (before)", - system_prompt: true, - marker: true, - enabled: true, - role: 0 - }, - { - identifier: "charDescription", - name: "Char Description", - system_prompt: true, - marker: true, - enabled: true, - role: 0 - }, - { - identifier: "charPersonality", - name: "Char Personality", - system_prompt: true, - marker: true, - enabled: true, - role: 0 - }, - { - identifier: "scenario", - name: "Scenario", - system_prompt: true, - marker: true, - enabled: true, - role: 0 - }, - { - identifier: "personaDescription", - name: "Persona Description", - system_prompt: true, - marker: true, - enabled: true, - role: 0 - } - ], - - // 从后端加载预设列表 - fetchPresets: async () => { - set({ isLoadingPresets: true }); - try { - const response = await fetch('/api/presets'); - const data = await response.json(); - - // 转换为预设对象数组 - const presetList = data.presets.map(preset => ({ - id: preset.name, - name: preset.name, - description: preset.description, - component_count: preset.component_count, - temperature: preset.temperature - })); - - set({ presets: presetList, isLoadingPresets: false }); - } catch (error) { - console.error('Failed to fetch presets:', error); - set({ isLoadingPresets: false }); - } - }, - - // 设置选中的预设 - setSelectedPreset: async (presetId) => { - try { - // 从后端获取预设的完整内容 - const response = await fetch(`/api/presets/${presetId}`); - const presetData = await response.json(); - - // 记录原始数据用于调试 - console.log('从后端获取的预设数据:', presetData); - - // 提取参数并更新状态,确保所有参数都有默认值 - const parameters = { - temperature: presetData.temperature !== undefined ? presetData.temperature : 1.0, - frequency_penalty: presetData.frequency_penalty !== undefined ? presetData.frequency_penalty : 0.0, - presence_penalty: presetData.presence_penalty !== undefined ? presetData.presence_penalty : 0.0, - top_p: presetData.top_p !== undefined ? presetData.top_p : 1.0, - top_k: presetData.top_k !== undefined ? presetData.top_k : 0, - max_context: presetData.openai_max_context !== undefined ? presetData.openai_max_context : - (presetData.max_context !== undefined ? presetData.max_context : 1000000), - max_tokens: presetData.openai_max_tokens !== undefined ? presetData.openai_max_tokens : - (presetData.max_tokens !== undefined ? presetData.max_tokens : 30000), - max_context_unlocked: presetData.max_context_unlocked !== undefined ? presetData.max_context_unlocked : false, - stream_openai: presetData.stream_openai !== undefined ? presetData.stream_openai : true, - seed: presetData.seed !== undefined ? presetData.seed : -1, - n: presetData.n !== undefined ? presetData.n : 1 - }; - - // 记录映射后的参数用于调试 - console.log('映射后的参数:', parameters); - - // 处理预设组件 - let components = []; - if (presetData.prompts && Array.isArray(presetData.prompts)) { - // 获取当前角色的prompt_order,添加更严格的检查 - const currentOrder = (presetData.prompt_order && - Array.isArray(presetData.prompt_order) && - presetData.prompt_order.length > 0 && - presetData.prompt_order[0] && - presetData.prompt_order[0].order && - Array.isArray(presetData.prompt_order[0].order)) - ? presetData.prompt_order[0].order - : []; - - // 根据prompt_order排序组件 - components = presetData.prompts.map(prompt => { - const orderItem = currentOrder.find(item => item && item.identifier === prompt.identifier); - return { - ...prompt, - enabled: orderItem ? orderItem.enabled : true, - role: prompt.role !== undefined ? prompt.role : (prompt.system_prompt ? 0 : 1) - }; - }); - - // 如果有prompt_order,按照它排序 - if (currentOrder.length > 0) { - components.sort((a, b) => { - const indexA = currentOrder.findIndex(item => item && item.identifier === a.identifier); - const indexB = currentOrder.findIndex(item => item && item.identifier === b.identifier); - return indexA - indexB; - }); - } - } - - - // 更新状态,确保参数容器展开 - set({ - selectedPreset: presetId, - parameters, - promptComponents: components, - isParametersExpanded: true // 确保参数容器展开 - }); - } catch (error) { - console.error('Failed to load preset:', error); - } - }, - - // 更新参数 - updateParameter: ({ name, value }) => set((state) => ({ - parameters: { ...state.parameters, [name]: value } - })), - - // 添加预设 - addPreset: (preset) => set((state) => ({ - presets: [...state.presets, preset] - })), - - // 保存当前设置为预设 - saveCurrentAsPreset: async ({ name }) => { - const state = get(); - try { - // 构建预设数据 - const presetData = { - ...state.parameters, - prompts: state.promptComponents.map(component => ({ - identifier: component.identifier, - name: component.name, - content: component.content || '', - role: component.role, - system_prompt: component.system_prompt, - marker: component.marker - })), - prompt_order: [{ - order: state.promptComponents.map(component => ({ - identifier: component.identifier, - enabled: component.enabled !== false - })) - }] - }; - - // 发送到后端 - const response = await fetch('/api/presets', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - preset_name: name, - ...presetData - }) - }); - - if (!response.ok) { - throw new Error('Failed to save preset'); - } - - const result = await response.json(); - - // 添加到本地预设列表 - const newPreset = { - id: name, - name, - description: '', - component_count: state.promptComponents.length, - temperature: state.parameters.temperature - }; - - set((state) => ({ - presets: [...state.presets, newPreset], - selectedPreset: name - })); - - return result; - } catch (error) { - console.error('Failed to save preset:', error); - throw error; - } - }, - - // 编辑预设名称 - editPresetName: async (presetId, newName) => { - try { - const response = await fetch(`/api/presets/${presetId}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - name: newName - }) - }); - - if (!response.ok) { - throw new Error('Failed to update preset name'); - } - - // 更新本地状态 - set((state) => ({ - presets: state.presets.map(preset => - preset.id === presetId ? { ...preset, name: newName } : preset - ) - })); - - return await response.json(); - } catch (error) { - console.error('Failed to update preset name:', error); - throw error; - } - }, - - // 切换参数设置折叠状态 - toggleParametersExpanded: () => set((state) => ({ - isParametersExpanded: !state.isParametersExpanded - })), - - // 设置预设组件列表 - setPromptComponents: (components) => set({ promptComponents: components }), - - // 更新组件 - updateComponent: (index, updatedComponent) => set((state) => { - const newComponents = [...state.promptComponents]; - newComponents[index] = { ...newComponents[index], ...updatedComponent }; - return { promptComponents: newComponents }; - }), - - // 切换组件启用状态 - toggleComponentEnabled: (index) => set((state) => { - const newComponents = [...state.promptComponents]; - newComponents[index] = { - ...newComponents[index], - enabled: !newComponents[index].enabled - }; - return { promptComponents: newComponents }; - }), - - // 添加新组件 - addComponent: (component) => set((state) => ({ - promptComponents: [...state.promptComponents, component] - })), - - // 删除组件 - removeComponent: (index) => set((state) => { - const newComponents = [...state.promptComponents]; - newComponents.splice(index, 1); - return { promptComponents: newComponents }; - }), - - // 移动组件位置 - moveComponent: (fromIndex, toIndex) => set((state) => { - const newComponents = [...state.promptComponents]; - const [movedComponent] = newComponents.splice(fromIndex, 1); - newComponents.splice(toIndex, 0, movedComponent); - return { promptComponents: newComponents }; - }), - - // 获取当前预设的prompt_order - getPromptOrder: () => { - const { promptComponents } = get(); - return promptComponents.map(component => ({ - identifier: component.identifier, - enabled: component.enabled !== false - })); - } -})); - -export default usePresetStore; diff --git a/frontend-react/src/Store/Slices/LeftTabsSlices/SideBarLeftSlice.jsx b/frontend-react/src/Store/Slices/LeftTabsSlices/SideBarLeftSlice.jsx deleted file mode 100644 index 47bd478..0000000 --- a/frontend-react/src/Store/Slices/LeftTabsSlices/SideBarLeftSlice.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import { create } from 'zustand'; - -const useSideBarLeftStore = create((set) => ({ - activeTab: 'gallery', - - tabs: [ - { id: 'gallery', label: '🖼️ 画廊' }, - { id: 'api', label: '🔌 API' }, - { id: 'presets', label: '📋 预设' }, - { id: 'worldbook', label: '🌍 世界书' } - ], - - setActiveTab: (tab) => set({ activeTab: tab }) -})); - -export default useSideBarLeftStore; diff --git a/frontend-react/src/Store/Slices/LeftTabsSlices/WorldBookSlice.jsx b/frontend-react/src/Store/Slices/LeftTabsSlices/WorldBookSlice.jsx deleted file mode 100644 index 3c9c4eb..0000000 --- a/frontend-react/src/Store/Slices/LeftTabsSlices/WorldBookSlice.jsx +++ /dev/null @@ -1,696 +0,0 @@ -import { create } from 'zustand'; - -// LocalStorage 键名 -const GLOBAL_WORLDBOOKS_KEY = 'global_worldbooks'; - -// 辅助函数:从 LocalStorage 加载全局世界书 -const loadGlobalWorldBooks = () => { - try { - const stored = localStorage.getItem(GLOBAL_WORLDBOOKS_KEY); - return stored ? JSON.parse(stored) : []; - } catch (error) { - console.error('加载全局世界书失败:', error); - return []; - } -}; - -// 辅助函数:保存全局世界书到 LocalStorage -const saveGlobalWorldBooks = (globalBooks) => { - try { - localStorage.setItem(GLOBAL_WORLDBOOKS_KEY, JSON.stringify(globalBooks)); - } catch (error) { - console.error('保存全局世界书失败:', error); - } -}; - -// 辅助函数:处理 API 响应 -const handleResponse = async (response) => { - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.detail || `请求失败: ${response.status}`); - } - return response.json().catch(() => ({})); -}; - -// 辅助函数:处理文件下载 -const handleFileDownload = async (response, filename) => { - const blob = await response.blob(); - const url = window.URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.setAttribute('download', filename); - document.body.appendChild(link); - link.click(); - link.parentNode.removeChild(link); - window.URL.revokeObjectURL(url); -}; - -// 创建世界书 store -const useWorldBookStore = create((set, get) => ({ - // 状态 - worldBooks: [], // 世界书列表 - globalWorldBooks: loadGlobalWorldBooks(), // 从 LocalStorage 初始化全局世界书列表 - currentWorldBook: null, // 当前选中的世界书 - currentEntries: [], // 当前世界书的条目列表 - currentEntry: null, // 当前选中的条目 - loading: false, // 加载状态 - error: null, // 错误信息 - success: false, // 操作成功状态 - message: '', // 成功或错误消息 - - // Actions - clearError: () => set({ error: null, message: '' }), - - clearSuccess: () => set({ success: false, message: '' }), - - setCurrentWorldBook: (worldBook) => set({ - currentWorldBook: worldBook, - currentEntries: [], - currentEntry: null - }), - - setCurrentEntry: (entry) => set({ currentEntry: entry }), - - resetCurrentWorldBook: () => set({ - currentWorldBook: null, - currentEntries: [], - currentEntry: null - }), - - // 异步操作:切换世界书的全局状态 - toggleGlobalWorldBook: async (name, isGlobal) => { - set({ loading: true, error: null, success: false }); - try { - const formData = new FormData(); - formData.append('is_global', isGlobal); - - const response = await fetch(`/api/worldbooks/${name}`, { - method: 'PUT', - body: formData - }); - - const data = await handleResponse(response); - - set(state => { - const updatedWorldBooks = state.worldBooks.map(wb => - wb.name === data.name ? data : wb - ); - - // 更新全局世界书列表 - let updatedGlobalBooks = [...state.globalWorldBooks]; - const globalIndex = updatedGlobalBooks.findIndex(wb => wb.name === data.name); - - if (isGlobal) { - // 如果是世界书被标记为全局 - if (globalIndex === -1) { - // 如果不在全局列表中,添加它 - updatedGlobalBooks = [...updatedGlobalBooks, data]; - } else { - // 如果已经在全局列表中,更新它 - updatedGlobalBooks[globalIndex] = data; - } - } else { - // 如果世界书不再全局,从全局列表中移除 - if (globalIndex !== -1) { - updatedGlobalBooks = updatedGlobalBooks.filter(wb => wb.name !== data.name); - } - } - - // 保存到 LocalStorage - saveGlobalWorldBooks(updatedGlobalBooks); - - return { - loading: false, - worldBooks: updatedWorldBooks, - globalWorldBooks: updatedGlobalBooks, - currentWorldBook: state.currentWorldBook?.name === data.name - ? data - : state.currentWorldBook, - success: true, - }; - }); - - return data; - } catch (error) { - set({ - loading: false, - error: error.message, - success: false - }); - throw error; - } - }, - - // 异步操作:获取所有世界书 - fetchWorldBooks: async () => { - set({ loading: true, error: null }); - try { - const response = await fetch(`/api/worldbooks/`); - const data = await handleResponse(response); - - // 从 LocalStorage 获取全局世界书列表 - const globalBooks = loadGlobalWorldBooks(); - - set({ - loading: false, - worldBooks: data, - globalWorldBooks: globalBooks, - error: null - }); - return data; - } catch (error) { - set({ - loading: false, - error: error.message - }); - throw error; - } - }, - - // 异步操作:获取指定世界书 - fetchWorldBook: async (name) => { - set({ loading: true, error: null }); - try { - const response = await fetch(`/api/worldbooks/${name}`); - const data = await handleResponse(response); - set({ - loading: false, - currentWorldBook: data, - error: null - }); - return data; - } catch (error) { - set({ - loading: false, - error: error.message - }); - throw error; - } - }, - - // 异步操作:创建世界书 - createWorldBook: async ({ name, is_global, file }) => { - set({ loading: true, error: null, success: false }); - try { - const formData = new FormData(); - formData.append('name', name); - if (is_global !== undefined) { - formData.append('is_global', is_global); - } - if (file) { - formData.append('file', file); - } - - const response = await fetch(`/api/worldbooks/`, { - method: 'POST', - body: formData - }); - - const data = await handleResponse(response); - - set(state => { - const newWorldBooks = [...state.worldBooks, data]; - let newGlobalBooks = [...state.globalWorldBooks]; - - // 如果是世界书被标记为全局,添加到全局列表 - if (data.is_global) { - newGlobalBooks = [...newGlobalBooks, data]; - saveGlobalWorldBooks(newGlobalBooks); - } - - return { - loading: false, - worldBooks: newWorldBooks, - globalWorldBooks: newGlobalBooks, - currentWorldBook: data, - success: true, - message: '世界书创建成功' - }; - }); - - return data; - } catch (error) { - set({ - loading: false, - error: error.message, - success: false - }); - throw error; - } - }, - - // 异步操作:更新世界书 - updateWorldBook: async ({ name, is_global, file }) => { - set({ loading: true, error: null, success: false }); - try { - const formData = new FormData(); - if (is_global !== undefined) { - formData.append('is_global', is_global); - } - if (file) { - formData.append('file', file); - } - - const response = await fetch(`/api/worldbooks/${name}`, { - method: 'PUT', - body: formData - }); - - const data = await handleResponse(response); - - set(state => { - const updatedWorldBooks = state.worldBooks.map(wb => - wb.name === data.name ? data : wb - ); - - // 更新全局世界书列表 - let updatedGlobalBooks = [...state.globalWorldBooks]; - const globalIndex = updatedGlobalBooks.findIndex(wb => wb.name === data.name); - - if (data.is_global) { - // 如果是世界书被标记为全局 - if (globalIndex === -1) { - // 如果不在全局列表中,添加它 - updatedGlobalBooks = [...updatedGlobalBooks, data]; - } else { - // 如果已经在全局列表中,更新它 - updatedGlobalBooks[globalIndex] = data; - } - } else { - // 如果世界书不再全局,从全局列表中移除 - if (globalIndex !== -1) { - updatedGlobalBooks = updatedGlobalBooks.filter(wb => wb.name !== data.name); - } - } - - // 保存到 LocalStorage - saveGlobalWorldBooks(updatedGlobalBooks); - - return { - loading: false, - worldBooks: updatedWorldBooks, - globalWorldBooks: updatedGlobalBooks, - currentWorldBook: state.currentWorldBook?.name === data.name - ? data - : state.currentWorldBook, - success: true, - message: '世界书更新成功' - }; - }); - - return data; - } catch (error) { - set({ - loading: false, - error: error.message, - success: false - }); - throw error; - } - }, - - // 异步操作:删除世界书 - deleteWorldBook: async (name) => { - set({ loading: true, error: null, success: false }); - try { - const response = await fetch(`/api/worldbooks/${name}`, { - method: 'DELETE' - }); - - await handleResponse(response); - - set(state => { - const filteredWorldBooks = state.worldBooks.filter(wb => wb.name !== name); - const filteredGlobalBooks = state.globalWorldBooks.filter(wb => wb.name !== name); - - // 保存到 LocalStorage - saveGlobalWorldBooks(filteredGlobalBooks); - - return { - loading: false, - worldBooks: filteredWorldBooks, - globalWorldBooks: filteredGlobalBooks, - currentWorldBook: state.currentWorldBook?.name === name - ? null - : state.currentWorldBook, - currentEntries: state.currentWorldBook?.name === name - ? [] - : state.currentEntries, - currentEntry: state.currentWorldBook?.name === name - ? null - : state.currentEntry, - success: true, - message: '世界书删除成功' - }; - }); - - return name; - } catch (error) { - set({ - loading: false, - error: error.message, - success: false - }); - throw error; - } - }, - - // 异步操作:获取世界书的所有条目 - fetchWorldBookEntries: async (name) => { - set({ loading: true, error: null }); - try { - const response = await fetch(`/api/worldbooks/${name}/entries`); - const data = await handleResponse(response); - - set(state => { - if (state.currentWorldBook?.name === name) { - return { - loading: false, - currentEntries: data, - error: null - }; - } - return { loading: false, error: null }; - }); - - return data; - } catch (error) { - set({ - loading: false, - error: error.message - }); - throw error; - } - }, - - // 异步操作:获取世界书的指定条目 - fetchWorldBookEntry: async (name, uid) => { - set({ loading: true, error: null }); - try { - const response = await fetch(`/api/worldbooks/${name}/entries/${uid}`); - const data = await handleResponse(response); - - set(state => { - if (state.currentWorldBook?.name === name) { - return { - loading: false, - currentEntry: data, - error: null - }; - } - return { loading: false, error: null }; - }); - - return data; - } catch (error) { - set({ - loading: false, - error: error.message - }); - throw error; - } - }, - - // 异步操作:创建世界书条目 - createWorldBookEntry: async (name, entryData) => { - set({ loading: true, error: null, success: false }); - try { - // 处理触发配置数据 - const processedEntryData = { ...entryData }; - if (processedEntryData.trigger_config && processedEntryData.trigger_config.triggers) { - // 创建新的触发配置对象 - const triggerConfig = { - triggers: {} - }; - - // 处理每个触发策略 - for (const [strategy, triggerInfo] of Object.entries(processedEntryData.trigger_config.triggers)) { - if (Array.isArray(triggerInfo) && triggerInfo.length >= 2) { - triggerConfig.triggers[strategy] = [ - triggerInfo[0], // 是否启用 - triggerInfo[1] // 配置对象 - ]; - } else { - // 如果格式不正确,设置为不启用 - triggerConfig.triggers[strategy] = [false, null]; - } - } - - processedEntryData.trigger_config = triggerConfig; - } - - const response = await fetch(`/api/worldbooks/${name}/entries`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(processedEntryData) - }); - - const data = await handleResponse(response); - - set(state => { - if (state.currentWorldBook?.name === name) { - return { - loading: false, - currentEntries: [...state.currentEntries, data], - success: true, - message: '条目创建成功' - }; - } - return { - loading: false, - success: true, - message: '条目创建成功' - }; - }); - - return data; - } catch (error) { - set({ - loading: false, - error: error.message, - success: false - }); - throw error; - } - }, - - // 异步操作:更新世界书条目 - updateWorldBookEntry: async (name, uid, entryData) => { - set({ loading: true, error: null, success: false }); - try { - // 处理触发配置数据 - const processedEntryData = { ...entryData }; - if (processedEntryData.trigger_config && processedEntryData.trigger_config.triggers) { - // 创建新的触发配置对象 - const triggerConfig = { - triggers: {} - }; - - // 处理每个触发策略 - for (const [strategy, triggerInfo] of Object.entries(processedEntryData.trigger_config.triggers)) { - if (Array.isArray(triggerInfo) && triggerInfo.length >= 2) { - triggerConfig.triggers[strategy] = [ - triggerInfo[0], // 是否启用 - triggerInfo[1] // 配置对象 - ]; - } else { - // 如果格式不正确,设置为不启用 - triggerConfig.triggers[strategy] = [false, null]; - } - } - - processedEntryData.trigger_config = triggerConfig; - } - - const response = await fetch(`/api/worldbooks/${name}/entries/${uid}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(processedEntryData) - }); - - const data = await handleResponse(response); - - set(state => { - if (state.currentWorldBook?.name === name) { - const updatedEntries = state.currentEntries.map(entry => - entry.uid === data.uid ? data : entry - ); - - return { - loading: false, - currentEntries: updatedEntries, - currentEntry: state.currentEntry?.uid === data.uid - ? data - : state.currentEntry, - success: true, - message: '条目更新成功' - }; - } - return { - loading: false, - success: true, - message: '条目更新成功' - }; - }); - - return data; - } catch (error) { - set({ - loading: false, - error: error.message, - success: false - }); - throw error; - } - }, - - // 异步操作:删除世界书条目 - deleteWorldBookEntry: async (name, uid) => { - set({ loading: true, error: null, success: false }); - try { - const response = await fetch(`/api/worldbooks/${name}/entries/${uid}`, { - method: 'DELETE' - }); - - await handleResponse(response); - - set(state => { - if (state.currentWorldBook?.name === name) { - const filteredEntries = state.currentEntries.filter(entry => entry.uid !== uid); - - return { - loading: false, - currentEntries: filteredEntries, - currentEntry: state.currentEntry?.uid === uid - ? null - : state.currentEntry, - success: true, - message: '条目删除成功' - }; - } - return { - loading: false, - success: true, - message: '条目删除成功' - }; - }); - - return { name, uid }; - } catch (error) { - set({ - loading: false, - error: error.message, - success: false - }); - throw error; - } - }, - - // 异步操作:导入世界书 - importWorldBook: async (name, file) => { - set({ loading: true, error: null, success: false }); - try { - const formData = new FormData(); - formData.append('file', file); - - const response = await fetch(`/api/worldbooks/${name}/import`, { - method: 'POST', - body: formData - }); - - const data = await handleResponse(response); - - set(state => { - const existingIndex = state.worldBooks.findIndex(wb => wb.name === data.name); - let updatedWorldBooks; - let updatedGlobalBooks = [...state.globalWorldBooks]; - - if (existingIndex !== -1) { - updatedWorldBooks = [...state.worldBooks]; - updatedWorldBooks[existingIndex] = data; - - // 更新全局世界书列表 - const globalIndex = updatedGlobalBooks.findIndex(wb => wb.name === data.name); - if (data.is_global) { - if (globalIndex === -1) { - updatedGlobalBooks = [...updatedGlobalBooks, data]; - } else { - updatedGlobalBooks[globalIndex] = data; - } - } else { - if (globalIndex !== -1) { - updatedGlobalBooks = updatedGlobalBooks.filter(wb => wb.name !== data.name); - } - } - } else { - updatedWorldBooks = [...state.worldBooks, data]; - - // 如果是世界书被标记为全局,添加到全局列表 - if (data.is_global) { - updatedGlobalBooks = [...updatedGlobalBooks, data]; - } - } - - // 保存到 LocalStorage - saveGlobalWorldBooks(updatedGlobalBooks); - - return { - loading: false, - worldBooks: updatedWorldBooks, - globalWorldBooks: updatedGlobalBooks, - currentWorldBook: state.currentWorldBook?.name === data.name - ? data - : state.currentWorldBook, - success: true, - message: '世界书导入成功' - }; - }); - - return data; - } catch (error) { - set({ - loading: false, - error: error.message, - success: false - }); - throw error; - } - }, - - // 异步操作:导出世界书 - exportWorldBook: async (name) => { - set({ loading: true, error: null, success: false }); - try { - const response = await fetch(`/api/worldbooks/${name}/export`); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.detail || `请求失败: ${response.status}`); - } - - // 处理文件下载 - await handleFileDownload(response, `${name}.json`); - - set({ - loading: false, - success: true, - message: '世界书导出成功' - }); - - return { name }; - } catch (error) { - set({ - loading: false, - error: error.message, - success: false - }); - throw error; - } - }, -})); - -export default useWorldBookStore; \ No newline at end of file diff --git a/frontend-react/src/Store/Slices/RightTabsSlices/SideBarRightSlice.jsx b/frontend-react/src/Store/Slices/RightTabsSlices/SideBarRightSlice.jsx deleted file mode 100644 index 15b11e0..0000000 --- a/frontend-react/src/Store/Slices/RightTabsSlices/SideBarRightSlice.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import { create } from 'zustand'; - -const useSideBarRightStore = create((set) => ({ - selectedTabs: ['dice', 'macros'], - - allTabs: [ - { id: 'dice', label: '🎲 骰子与工具', component: null }, - { id: 'debug', label: '🔍 上下文调试', component: null }, - { id: 'macros', label: '🔧 快捷宏', component: null }, - { id: 'table', label: '📊 动态表格', component: null } - ], - - handleTabClick: (tabId) => set((state) => { - if (state.selectedTabs.includes(tabId)) { - // 如果已选中,则取消选中 - return { selectedTabs: state.selectedTabs.filter(id => id !== tabId) }; - } else if (state.selectedTabs.length < 2) { - // 如果未选中且少于2个,则添加 - return { selectedTabs: [...state.selectedTabs, tabId] }; - } else { - // 如果已有2个,则替换最早选中的 - return { selectedTabs: [...state.selectedTabs.slice(1), tabId] }; - } - }), - - // 设置特定标签的组件 - setTabComponent: (tabId, component) => set((state) => ({ - allTabs: state.allTabs.map(tab => - tab.id === tabId ? { ...tab, component } : tab - ) - })) -})); - -export default useSideBarRightStore; diff --git a/frontend-react/src/Store/Slices/RoleSelectorSlice.jsx b/frontend-react/src/Store/Slices/RoleSelectorSlice.jsx deleted file mode 100644 index 6b287b4..0000000 --- a/frontend-react/src/Store/Slices/RoleSelectorSlice.jsx +++ /dev/null @@ -1,325 +0,0 @@ -import { create } from 'zustand'; - -// 异步获取角色数据 -const fetchRoleData = async () => { - try { - const response = await fetch('/api/chats', { - method: 'GET', - headers: { - 'Cache-Control': 'no-cache', - 'Pragma': 'no-cache', - 'Expires': '0' - } - }); - const data = await response.json(); - - // 转换数据格式以适应前端需求 - const roleData = {}; - if (data.chats && Array.isArray(data.chats)) { - data.chats.forEach(chat => { - if (!roleData[chat.role_name]) { - roleData[chat.role_name] = []; - } - roleData[chat.role_name].push(chat.chat_name); - }); - } - - return roleData; - } catch (error) { - console.error('获取角色数据失败:', error); - throw error; - } -}; - -const useRoleSelectorStore = create((set, get) => ({ - // 状态 - roleData: {}, - selectedRole: null, - selectedChat: null, - hoveredRole: null, - clickedRole: null, - isLoading: false, - searchTerm: '', - editingRole: null, - editingChat: null, - showDeleteConfirm: null, - deleteType: null, - - // 操作 - fetchRoleData: async () => { - set({ isLoading: true }); - try { - const data = await fetchRoleData(); - set({ roleData: data, isLoading: false }); - } catch (error) { - set({ isLoading: false }); - console.error('获取角色数据失败:', error); - } - }, - - setSelectedRole: (role) => set({ selectedRole: role }), - - setSelectedChat: (chat) => set({ selectedChat: chat }), - - setHoveredRole: (role) => set({ hoveredRole: role }), - - setClickedRole: (role) => set({ clickedRole: role }), - - setSearchTerm: (term) => set({ searchTerm: term }), - - setEditingRole: (role) => set({ editingRole: role }), - - setEditingChat: (chat) => set({ editingChat: chat }), - - setShowDeleteConfirm: (name) => set({ showDeleteConfirm: name }), - - setDeleteType: (type) => set({ deleteType: type }), - - // 同时更新角色和聊天 - setSelectedRoleAndChat: (role, chat) => set({ selectedRole: role, selectedChat: chat }), - - // 处理角色重命名 - handleRenameRole: async (oldName, newName) => { - const { roleData, selectedRole } = get(); - if (newName && newName !== oldName) { - try { - // 获取该角色下的所有聊天 - const chats = roleData[oldName] || []; - - // 为每个聊天更新元数据中的角色名称 - for (const chatName of chats) { - await fetch(`/api/chats/${encodeURIComponent(oldName)}/${encodeURIComponent(chatName)}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - metadata: { character_name: newName } - }) - }); - } - - // 更新本地状态 - const newRoleData = { ...roleData }; - newRoleData[newName] = chats; - delete newRoleData[oldName]; - - if (selectedRole === oldName) { - set({ - roleData: newRoleData, - selectedRole: newName, - editingRole: null - }); - } else { - set({ - roleData: newRoleData, - editingRole: null - }); - } - } catch (error) { - console.error('重命名角色失败:', error); - set({ editingRole: null }); - } - } else { - set({ editingRole: null }); - } - }, - - // 处理聊天重命名 - handleRenameChat: async (oldName, newName) => { - const { roleData, selectedRole, selectedChat } = get(); - if (newName && newName !== oldName) { - try { - // 更新后端聊天名称 - await fetch(`/api/chats/${encodeURIComponent(selectedRole)}/${encodeURIComponent(oldName)}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - metadata: { chat_name: newName } - }) - }); - - // 更新本地状态 - const newRoleData = { ...roleData }; - const chatIndex = newRoleData[selectedRole].indexOf(oldName); - if (chatIndex !== -1) { - newRoleData[selectedRole][chatIndex] = newName; - - if (selectedChat === oldName) { - set({ - roleData: newRoleData, - selectedChat: newName, - editingChat: null - }); - } else { - set({ - roleData: newRoleData, - editingChat: null - }); - } - } else { - set({ editingChat: null }); - } - } catch (error) { - console.error('重命名聊天失败:', error); - set({ editingChat: null }); - } - } else { - set({ editingChat: null }); - } - }, - - // 确认删除 - confirmDelete: async () => { - const { roleData, selectedRole, selectedChat, showDeleteConfirm, deleteType } = get(); - - try { - if (deleteType === 'role') { - // 删除角色下的所有聊天 - const chats = roleData[showDeleteConfirm] || []; - for (const chatName of chats) { - await fetch(`/api/chats/${encodeURIComponent(showDeleteConfirm)}/${encodeURIComponent(chatName)}`, { - method: 'DELETE' - }); - } - - const newRoleData = { ...roleData }; - delete newRoleData[showDeleteConfirm]; - - if (selectedRole === showDeleteConfirm) { - set({ - roleData: newRoleData, - selectedRole: null, - selectedChat: null, - showDeleteConfirm: null, - deleteType: null - }); - } else { - set({ - roleData: newRoleData, - showDeleteConfirm: null, - deleteType: null - }); - } - } else if (deleteType === 'chat') { - // 删除单个聊天 - await fetch(`/api/chats/${encodeURIComponent(selectedRole)}/${encodeURIComponent(showDeleteConfirm)}`, { - method: 'DELETE' - }); - - const newRoleData = { ...roleData }; - const chatIndex = newRoleData[selectedRole].indexOf(showDeleteConfirm); - if (chatIndex !== -1) { - newRoleData[selectedRole].splice(chatIndex, 1); - - if (selectedChat === showDeleteConfirm) { - set({ - roleData: newRoleData, - selectedChat: null, - showDeleteConfirm: null, - deleteType: null - }); - } else { - set({ - roleData: newRoleData, - showDeleteConfirm: null, - deleteType: null - }); - } - } else { - set({ - showDeleteConfirm: null, - deleteType: null - }); - } - } - } catch (error) { - console.error('删除失败:', error); - set({ - showDeleteConfirm: null, - deleteType: null - }); - } - }, - - // 取消删除 - cancelDelete: () => set({ showDeleteConfirm: null, deleteType: null }), - - // 添加新角色 - handleAddRole: async () => { - const { roleData } = get(); - const newRole = '新角色'; - - try { - // 创建新角色(通过创建一个默认聊天) - await fetch(`/api/chats/${encodeURIComponent(newRole)}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - chat_name: '默认聊天', - metadata: { - user_name: 'User', - character_name: newRole - } - }) - }); - - const newRoleData = { ...roleData }; - newRoleData[newRole] = ['默认聊天']; - set({ - roleData: newRoleData, - editingRole: newRole - }); - } catch (error) { - console.error('添加角色失败:', error); - } - }, - - // 添加新聊天 - handleAddChat: async () => { - const { roleData, selectedRole } = get(); - if (!selectedRole) return; - - const newChat = '新聊天'; - - try { - await fetch(`/api/chats/${encodeURIComponent(selectedRole)}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - chat_name: newChat, - metadata: { - user_name: 'User', - character_name: selectedRole - } - }) - }); - - const newRoleData = { ...roleData }; - newRoleData[selectedRole].push(newChat); - set({ - roleData: newRoleData, - editingChat: newChat - }); - } catch (error) { - console.error('添加聊天失败:', error); - } - }, - - // 重置面板状态 - resetPanel: () => set({ - hoveredRole: null, - clickedRole: null, - editingRole: null, - editingChat: null, - showDeleteConfirm: null - }) -})); - -export default useRoleSelectorStore; \ No newline at end of file diff --git a/frontend-react/src/Store/indexStore.jsx b/frontend-react/src/Store/indexStore.jsx deleted file mode 100644 index 29412f8..0000000 --- a/frontend-react/src/Store/indexStore.jsx +++ /dev/null @@ -1,5 +0,0 @@ -// frontend-react/src/store/index.js -export { default as useRoleSelectorStore } from './Slices/RoleSelectorSlice'; -export { default as useSideBarLeftStore } from './Slices/LeftTabsSlices/SideBarLeftSlice'; -export { default as useSideBarRightStore } from './Slices/RightTabsSlices/SideBarRightSlice'; -export { default as useChatBoxStore } from './Slices/ChatBoxSlice'; \ No newline at end of file diff --git a/frontend-react/src/components/ChatBox/ChatBox.css b/frontend-react/src/components/ChatBox/ChatBox.css deleted file mode 100644 index 2e84193..0000000 --- a/frontend-react/src/components/ChatBox/ChatBox.css +++ /dev/null @@ -1,583 +0,0 @@ -/* ==================== 聊天框区域 ==================== */ - -/* React 组件根容器 */ -.chat-box { - height: 100%; /* 修改为100%,填满父容器 */ - width: 100%; - display: flex; - flex-direction: column; - background-color: #fafafa; - overflow: hidden; /* 防止内容溢出 */ -} - -/* 消息列表容器 */ -.chat-messages { - flex: 1; - min-height: 0; - overflow-y: auto; - padding: 20px; - padding-top: 60px; - display: flex; - flex-direction: column; - gap: 15px; -} - -/* 设置面板 */ -.settings-panel { - position: absolute; - top: 0; - right: 0; - z-index: 20; - background-color: #fff; - border-bottom-left-radius: 8px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); - overflow: hidden; - transition: all 0.3s ease; - max-width: 200px; -} - -.settings-panel.collapsed { - width: 40px; - height: 40px; - cursor: pointer; -} - -.settings-panel.expanded { - width: auto; - min-width: 150px; - padding: 10px; - border: 1px solid #eee; - border-top: none; - border-right: none; -} - -.settings-header { - height: 40px; - width: 100%; - display: flex; - align-items: center; - justify-content: center; - background-color: #f0f0f0; - cursor: pointer; - font-size: 18px; - color: #666; -} - -.settings-panel.collapsed .settings-header:hover { - background-color: #e0e0e0; -} - -.settings-options { - display: none; - flex-direction: column; - gap: 10px; - padding-top: 10px; -} - -.settings-panel.expanded .settings-options { - display: flex; -} - -.setting-item { - display: flex; - align-items: center; - justify-content: space-between; - font-size: 14px; - color: #333; -} - -.setting-item input[type="checkbox"] { - margin-right: 8px; - cursor: pointer; -} - -/* ==================== 聊天气泡样式 ==================== */ - -/* 消息行容器 */ -.message { - display: flex; - width: 100%; - margin-bottom: 10px; -} - -/* 气泡本体 */ -.message .bubble { - max-width: 100%; - padding: 10px 15px; - border-radius: 12px; - position: relative; - font-size: 14px; - line-height: 1.6; - word-wrap: break-word; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); - /* 新增:确保内容区域可以容纳swipe控件 */ - display: flex; - flex-direction: column; -} - -/* 区分左右布局 */ -.message.user { - justify-content: flex-end; /* 用户消息靠右 */ -} - -.message.ai { - justify-content: flex-start; /* AI 消息靠左 */ -} - -/* 消息容器 - 调整为垂直排列 */ -.message-container { - display: flex; - flex-direction: column; - max-width: 70%; -} - -/* 消息头部 - 包含名称和工具栏 */ -.message-header { - display: flex; - align-items: center; - margin-bottom: 5px; - order: 1; /* 确保头部显示在第一个位置 */ -} - -/* 消息名称 */ -.message-name { - font-size: 14px; - font-weight: 500; - color: #333; - padding: 2px 8px; - border-radius: 12px; - background-color: rgba(0, 0, 0, 0.05); - display: inline-block; -} - -/* 用户消息名称 */ -.message.user .message-name { - background-color: rgba(24, 144, 255, 0.1); - color: #1890ff; -} - -/* AI消息名称 */ -.message.ai .message-name { - background-color: rgba(0, 0, 0, 0.05); - color: #333; -} - -/* 消息工具栏 - 优化布局使图标更紧凑 */ -.message-toolbar { - display: flex; - align-items: center; - padding: 0 5px; - opacity: 0; - transition: opacity 0.2s ease; -} - -.message:hover .message-toolbar { - opacity: 1; -} - -.message-id { - font-size: 12px; - color: #888; - padding: 2px 6px; - border-radius: 4px; - background-color: rgba(0, 0, 0, 0.05); - display: flex; - align-items: center; -} - -.toolbar-buttons { - display: flex; - gap: 3px; /* 减少图标之间的间距 */ - margin: 0 5px; -} - -.toolbar-button { - display: flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - border: none; - background-color: rgba(0, 0, 0, 0.05); - border-radius: 4px; - cursor: pointer; - color: #555; - transition: all 0.2s ease; -} - -.toolbar-button:hover { - background-color: rgba(0, 0, 0, 0.1); - color: #333; -} - -/* AI消息头部布局:AI助手 - ID - 编辑 - 更多 */ -.message.ai .message-header { - flex-direction: row; - justify-content: flex-start; -} - -.message.ai .message-name { - order: 1; -} - -.message.ai .message-id { - order: 2; - margin-left: 5px; -} - -.message.ai .toolbar-buttons { - order: 3; - margin-left: 5px; -} - -/* 用户消息头部布局:更多 - 编辑 - ID - 我 */ -.message.user .message-header { - flex-direction: row; - justify-content: flex-end; -} - -.message.user .message-name { - order: 4; -} - -.message.user .message-id { - order: 3; - margin-right: 5px; -} - -.message.user .toolbar-buttons { - order: 1; - margin-right: 5px; -} - -/* 消息内容 - 确保显示在名称下方 */ -.message-content { - display: flex; - flex-direction: column; - max-width: 100%; - order: 2; /* 确保内容显示在第二个位置 */ -} - -/* 气泡本体 */ -.message .bubble { - max-width: 100%; - padding: 10px 15px; - border-radius: 12px; - position: relative; - font-size: 14px; - line-height: 1.6; - word-wrap: break-word; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} - -/* AI 气泡样式 */ -.message.ai .bubble { - background-color: #fff; - color: #333; - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; - border: 1px solid #eee; -} - -/* 用户气泡样式 */ -.message.user .bubble { - background-color: #1890ff; - color: #fff; - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; -} - -/* 针对 AI 消息中的 HTML 内容进行简单的样式重置 */ -.message.ai .bubble p { - margin: 0 0 8px 0; -} -.message.ai .bubble p:last-child { - margin-bottom: 0; -} -.message.ai .bubble ul, .message.ai .bubble ol { - margin: 0 0 8px 0; - padding-left: 20px; -} -.message.ai .bubble b { - font-weight: 600; - color: #000; -} - -/* 编辑模式 */ -.edit-container { - display: flex; - flex-direction: column; - gap: 10px; -} - -.edit-textarea { - width: 100%; - min-height: 80px; - padding: 10px; - border: 1px solid #ddd; - border-radius: 8px; - resize: vertical; - font-family: inherit; - font-size: 14px; - line-height: 1.5; -} - -.edit-textarea:focus { - outline: none; - border-color: #1890ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); -} - -.edit-buttons { - display: flex; - justify-content: flex-end; - gap: 8px; -} - -.save-button, .cancel-button { - display: flex; - align-items: center; - gap: 4px; - padding: 6px 12px; - border: none; - border-radius: 4px; - font-size: 14px; - cursor: pointer; - transition: all 0.2s ease; -} - -.save-button { - background-color: #1890ff; - color: white; -} - -.save-button:hover { - background-color: #40a9ff; -} - -.cancel-button { - background-color: #f0f0f0; - color: #333; -} - -.cancel-button:hover { - background-color: #e6e6e6; -} - -/* ==================== 输入区域 ==================== */ - -.chat-input-wrapper { - background-color: #f9f9f9; - border-top: 1px solid #ddd; - padding: 10px 20px; - height: 62px; /* 明确设置高度 */ - flex-shrink: 0; /* 防止被压缩 */ - display: flex; - align-items: flex-end; - gap: 10px; - width: 100%; - position: relative; - z-index: 10; -} - - -.chat-input-area { - flex: 1; - display: flex; -} - -.chat-input-area textarea { - width: 100%; - height: 42px; - min-height: 42px; - max-height: 300px; - padding: 10px; - border: 1px solid #ddd; - border-radius: 4px; - resize: none; - outline: none; - font-family: inherit; - line-height: 1.5; - overflow-y: auto; - display: block; -} - -.send-button { - height: 42px; - padding: 0 20px; - background-color: #1890ff; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - white-space: nowrap; - flex-shrink: 0; -} - -.send-button:hover { - background-color: #40a9ff; -} - -/* 加载状态样式 */ -.loading { - display: flex; - justify-content: center; - align-items: center; - padding: 20px; - color: #888; - font-size: 14px; -} - -/* 错误信息样式 */ -.error { - display: flex; - justify-content: center; - align-items: center; - padding: 20px; - color: #f5222d; - font-size: 14px; - background-color: rgba(245, 34, 45, 0.05); - border-radius: 4px; - margin: 10px 0; -} - -/* 确保消息名称和工具栏在移动设备上也能正常显示 */ -@media (max-width: 768px) { - .message-container { - max-width: 85%; - } - - .message-header { - flex-wrap: wrap; - } - - .message-toolbar { - opacity: 1; /* 在移动设备上始终显示工具栏 */ - } -} - -/* 添加滚动条样式,使其更美观 */ -.chat-messages::-webkit-scrollbar { - width: 6px; -} - -.chat-messages::-webkit-scrollbar-track { - background: #f1f1f1; -} - -.chat-messages::-webkit-scrollbar-thumb { - background: #c1c1c1; - border-radius: 3px; -} - -.chat-messages::-webkit-scrollbar-thumb:hover { - background: #a8a8a8; -} - -/* 确保消息内容中的链接样式 */ -.bubble a { - color: inherit; - text-decoration: underline; -} - -/* 代码块样式 */ -.bubble pre { - background-color: #f6f8fa; - padding: 10px; - border-radius: 6px; - overflow-x: auto; - margin: 8px 0; -} - -.bubble code { - font-family: 'Courier New', Courier, monospace; - font-size: 0.9em; -} - -.options-button { - height: 42px; - width: 42px; - background-color: transparent; - border: 1px solid #ddd; - border-radius: 4px; - cursor: pointer; - font-size: 20px; - color: #666; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} - -.options-button:hover { - background-color: #f0f0f0; - color: #333; -} - -.send-button { - height: 42px; - width: 42px; /* 添加固定宽度,使按钮为正方形 */ - padding: 0; - background-color: #1890ff; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - display: flex; /* 添加 flex 布局 */ - align-items: center; /* 垂直居中 */ - justify-content: center; /* 水平居中 */ - flex-shrink: 0; - font-size: 18px; /* 设置图标大小 */ -} - -.send-button:hover { - background-color: #40a9ff; -} - -.send-button.stopping { - background-color: #ff4d4f; /* 终止状态下的背景色 */ -} - -.send-button.stopping:hover { - background-color: #ff7875; -} - -/* Swipe控制按钮样式 */ -.swipe-controls { - display: flex; - align-items: center; - justify-content: center; - margin-top: 8px; - gap: 8px; -} - -.swipe-button { - background-color: rgba(0, 0, 0, 0.05); - border: none; - border-radius: 4px; - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - color: #555; - transition: all 0.2s ease; - font-size: 12px; -} - -.swipe-button:hover:not(:disabled) { - background-color: rgba(0, 0, 0, 0.1); - color: #333; -} - -.swipe-button:disabled { - opacity: 0.3; - cursor: not-allowed; -} - -.swipe-counter { - font-size: 12px; - color: #888; - padding: 0 4px; -} diff --git a/frontend-react/src/components/ChatBox/ChatBox.jsx b/frontend-react/src/components/ChatBox/ChatBox.jsx deleted file mode 100644 index 82b18e6..0000000 --- a/frontend-react/src/components/ChatBox/ChatBox.jsx +++ /dev/null @@ -1,254 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; -import useChatBoxStore from '../../Store/Slices/ChatBoxSlice'; -import './ChatBox.css'; - -const ChatBox = () => { - const [editingId, setEditingId] = useState(null); - const [editContent, setEditContent] = useState(''); - const messagesEndRef = useRef(null); - const [inputValue, setInputValue] = useState(''); - - // 新增:管理每条消息的当前显示的swipe版本 - const [currentSwipeId, setCurrentSwipeId] = useState({}); - - // 从 ChatBoxStore 获取状态和方法 - const { - messages, - isLoading, - error, - userName, - characterName, - updateMessage, - isGenerating, - sendMessage, - stopGeneration - } = useChatBoxStore(); - - const [inputHeight, setInputHeight] = useState(42); - - // 添加输入框高度自适应处理 - const handleInputHeight = (e) => { - const textarea = e.target; - const newHeight = Math.min(Math.max(textarea.scrollHeight, 42), 300); - setInputHeight(newHeight); - }; - - // 处理发送或终止 - const handleSendOrStop = () => { - if (isGenerating) { - stopGeneration(); - } else { - sendMessage(inputValue); - setInputValue(''); - setInputHeight(42); - } - }; - - // 处理键盘事件 - const handleKeyDown = (e) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - handleSendOrStop(); - } - }; - - // 自动滚动到底部 - const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); - }; - - useEffect(() => { - scrollToBottom(); - }, [messages]); - - // 处理编辑消息 - const handleEdit = (message) => { - setEditingId(message.floor); - setEditContent(message.mes); - }; - - // 保存编辑 - const handleSaveEdit = (messageId) => { - // 调用 store 中的 updateMessage 方法 - updateMessage(messageId, editContent); - setEditingId(null); - setEditContent(''); - }; - - // 取消编辑 - const handleCancelEdit = () => { - setEditingId(null); - setEditContent(''); - }; - - // 新增:处理swipe切换 - const handleSwipeChange = (messageId, direction) => { - const message = messages.find(m => m.floor === messageId); - if (message && message.swipes && message.swipes.length > 0) { - const currentIndex = currentSwipeId[messageId] !== undefined - ? currentSwipeId[messageId] - : message.swipe_id; - const newIndex = currentIndex + direction; - - if (newIndex >= 0 && newIndex < message.swipes.length) { - setCurrentSwipeId(prev => ({ - ...prev, - [messageId]: newIndex - })); - } - } - }; - - // 渲染单条消息 - const renderMessage = (message) => { - const isUser = message.is_user; - const isEditing = editingId === message.floor; - - // 判断是否为最新消息 - const isLatestMessage = messages.length > 0 && message.floor === messages[messages.length - 1].floor; - - // 根据消息类型设置显示名称 - const displayName = isUser ? userName : characterName; - - // 确定当前显示的消息内容 - let currentMes = message.mes; - let hasSwipes = message.swipes && message.swipes.length > 0; - let currentSwipeIndex = message.swipe_id; - - if (hasSwipes) { - // 如果有swipes数组 - if (currentSwipeId[message.floor] !== undefined) { - // 如果用户已经切换过版本,使用用户选择的版本 - currentSwipeIndex = currentSwipeId[message.floor]; - } else { - // 否则使用默认的swipe_id - currentSwipeIndex = message.swipe_id; - } - - if (currentSwipeIndex >= 0 && currentSwipeIndex < message.swipes.length) { - currentMes = message.swipes[currentSwipeIndex]; - } - } - - return ( -
-
-
- {displayName} - #{message.floor} -
-
- - -
-
-
-
- {isEditing ? ( -
-