diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/doc-forge.iml b/.idea/doc-forge.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/doc-forge.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..53fab56 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..18b9e70 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ed1e887 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,58 @@ +# AGENTS.md — doc-forge + +你正在参与一个外包项目,客户需要一套完整的 AI 文档模板生成系统。 + +## 你的角色 +全栈开发 AI 助手,负责生成 Vue 3 + Ant Design Vue 前端代码和 Python FastAPI 后端代码。 + +## 基础设施 + +### MySQL +- 数据库名:`doc_forge`,字符集 `utf8mb4` +- 异步驱动:`asyncmy` +- 连接池:pool_size=10, max_overflow=20 +- 本地开发:`docker-compose up mysql` +- DDL 见 `init.sql` + +### MinIO(对象存储) +- 三个 bucket:`doc-forge-templates`(模板)/ `doc-forge-uploads`(参考文件)/ `doc-forge-outputs`(导出文档) +- 文件路径规则:`{bucket}/{YYYYMMDD}/{uuid}.{ext}` +- 预签名 URL 用于前端下载,过期 1 小时 +- 本地开发:`docker-compose up minio`,Console http://localhost:9001 +- SDK:`from minio import Minio`,客户端在 `services/minio_client.py` + +### Docker +- `docker-compose up -d mysql minio` 启动开发依赖 +- `docker-compose up backend web` 启动全栈 + +## 通讯协议 +- 所有 API 响应格式:`{ code: 0, data: {...}, message: "ok" }` +- 错误响应:`{ code: -1, message: "错误描述" }` +- 分页响应:`{ code: 0, data: { items: [], total: N, page: 1, page_size: 20 } }` + +## 必须遵守的规则 + +1. **AI 输出格式** — AI 必须返回 JSON,不得返回纯文本。前端解析 `content` 数组,按 type 分段渲染。 +2. **Word 导出** — 严禁重新生成文档。从 MinIO 拉取原始模板,只替换对应位置的文本节点。 +3. **段落边界** — 只认 Word 标题样式(Heading)。不要尝试用正则或关键词判断段落。 +4. **API Key 安全** — 所有 API Key 用 `cryptography.fernet.Fernet` 加密存储,前端只展示脱敏字符串。 +5. **并发控制** — 段落生成使用 `asyncio.gather` + `Semaphore`,单文档最大并发 5。 +6. **文件存储** — 所有用户文件存 MinIO,后端本地只做临时缓存。 +7. docs/规范与约束/开发规范.md +8. docs/需求与设计/02-模板格式规范.md + +## 段落配置字段 +每个 paragraph 包含: +- `edit_mode`: 'manual' | 'ai' +- `model_id`: int | null(null 表示使用系统默认模型) +- `need_prompt`: boolean + `prompt_text`: string +- `need_file`: boolean + `file_note`: string(备注提示上传什么文件) +- `output_format`: 'text' | 'table' | 'mixed' | 'chart' + +## 容易踩的坑 +- python-docx 中文字体名在 `run.fonts.eastAsia`,不是 `run.fonts.name` +- Ant Design Vue 4.x 的 modal 使用 `v-model:open`,不是 `v-model:visible` +- SSE 事件流要用 `sse-starlette` 的 `EventSourceResponse` +- asyncio 中不能混用同步的 openpyxl,Excel 解析放在线程池执行 (`run_in_executor`) +- MinIO SDK 是同步的,用 `run_in_executor` 包装,不要直接 in asyncio +- asyncmy 连接 MySQL 需要 `charset=utf8mb4`,不然中文会乱码 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d1c8a9f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,67 @@ +# doc-forge — AI 文档模板生成系统 + +## 项目概述 +上传 Word 模板 → AI 自动解析段落(按标题样式切割)→ 用户标注段落配置 → 上传参考文件 → AI 多段落并行生成 → 预览编辑 → 导出 Word(保留原始样式)。 + +## 技术栈 +| 层 | 技术 | 说明 | +|----|------|------| +| 前端 | Vue 3.4 + TypeScript + Ant Design Vue 4.x + Pinia + Vite 5 | web/ | +| 后端 | Python 3.11+ + FastAPI + SQLAlchemy 2.0 async | backend/app/ | +| 数据库 | MySQL 8.0(asyncmy 驱动) | docker-compose mysql | +| 对象存储 | MinIO | 存模板文件、参考文件、生成文档 | +| Word 处理 | python-docx | 解析/导出 | +| Excel 处理 | openpyxl | 解析参考文件 | + +## 目录结构 +``` +doc-forge/ +├── web/ 前端 +│ └── src/ +│ ├── views/ TemplateList, TemplateEditor, ModelManage, GeneratePage, HistoryPage, PreviewEdit +│ ├── components/ ParagraphList, ParagraphConfig, DocPreview, FileUploader, ModelModal, TestModal +│ ├── api/ Axios(template, model, generate) +│ ├── stores/ Pinia(template, model, document) +│ └── router/ 6 条路由 +├── backend/ +│ ├── app/ +│ │ ├── models/ ORM(template, paragraph, ai_model, document, generation_log) +│ │ ├── routers/ API(templates, models, generate, export) +│ │ ├── schemas/ Pydantic 校验 +│ │ ├── services/ 业务逻辑(parser, ai_service, generator, exporter, minio_client) +│ │ └── main.py +│ ├── config.py 配置(MySQL + MinIO + AI) +│ └── database.py 异步引擎 +├── docs/ 项目文档 +├── docker-compose.yml MySQL + MinIO + backend + web +├── init.sql 数据库建表 DDL +├── CLAUDE.md +└── AGENTS.md +``` + +## 关键约定 + +### 段落解析规则 +- 段落边界由 Word 标题样式(Heading 1~6)确定 +- 标题与下一标题之间的正文、表格归属到该标题段落 +- 表格独立存储为 `is_table=True`,归属于前一个标题 + +### AI 输出格式 +AI 必须返回结构化 JSON: +```json +{"content": [{"type": "text", "text": "..."}, {"type": "table", "headers": [], "rows": []}]} +``` + +### 文件存储 +- 所有文件存 MinIO,不存本地磁盘 +- bucket 分三类:templates / uploads / outputs +- MinIO 开发环境在 docker-compose 中启动 + +### AI 模型调用 +- OpenAI 格式:GPT-4o, DeepSeek-V3, 通义千问 +- Anthropic 格式:Claude 3.5 Sonnet +- 超时 60s,最多重试 3 次,单文档最大并发 5 + +### Word 导出 +- 从 MinIO 拉取原始模板 → 在内存中修改 → 上传回 MinIO +- 样式完全保留(字体/颜色/行距/页边距/页眉页脚) diff --git a/README.md b/README.md index 887871c..1b9dd5c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,80 @@ # doc-forge -《文档锻造》 \ No newline at end of file +上传 Word 模板 → AI 逐段落生成 → 导出保留原始样式的 Word 文档。 + +## 技术栈 + +- **前端**: Vue 3.4 + Vite 5 + TypeScript + Ant Design Vue 4.x + Pinia + Axios +- **后端**: Python 3.11+ + FastAPI + SQLAlchemy 2.0 async +- **数据库**: MySQL 8.0(asyncmy 驱动) +- **对象存储**: MinIO(模板/参考文件/导出文档) +- **文档处理**: python-docx、openpyxl + +## 快速开始 + +```bash +# 1. 启动基础设施(MySQL + MinIO) +docker-compose up -d mysql minio + +# 2. 启动后端 +cd backend +pip install -r requirements.txt +python main.py +# → http://localhost:8000/docs Swagger + +# 3. 启动前端 +cd web +pnpm install +pnpm dev +# → http://localhost:5173 +``` + +## 目录结构 + +``` +doc-forge/ +├── web/ Vue 3 前端 +│ └── src/ +│ ├── views/ 6 个页面 +│ ├── components/ 6 个通用组件 +│ ├── api/ Axios 请求层 +│ ├── stores/ Pinia 状态管理 +│ └── router/ 路由配置 +├── backend/ Python FastAPI 后端 +│ ├── app/models/ ORM 数据模型 +│ ├── app/routers/ API 路由 +│ ├── app/schemas/ Pydantic 校验 +│ ├── app/services/ 业务逻辑层 +│ ├── config.py 配置 +│ └── database.py 异步数据库引擎 +├── docker-compose.yml MySQL + MinIO + 后端 + 前端 +├── init.sql 数据库建表 DDL +└── docs/ 项目文档 +``` + +## 核心流程 + +``` +上传模板 → 解析段落 → 标注配置 → 保存模板 + → 执行生成(上传文件 → AI 并行生成 → SSE 进度) + → 预览编辑 → 导出 Word(保留原始样式) +``` + +## 关键设计 + +| 决策 | 选择 | 原因 | +|------|------|------| +| 段落边界 | Word 标题样式 Heading 1~6 | 稳定可靠,用户学习成本低 | +| AI 输出 | 结构化 JSON | 支持文字+表格混合,后端可控解析 | +| 导出策略 | 基于原模板替换内容 | 样式零损失,不限模板格式 | +| 文件存储 | MinIO 对象存储 | 可扩展,不占用本地磁盘 | +| 数据库 | MySQL 8.0 | 生产级可靠性 | +| 生成方式 | 多段落并行 asyncio.gather | 大幅缩短等待时间 | + +## 环境要求 + +- Python 3.11+ +- Node.js 18+ +- pnpm 8+ +- Docker + docker-compose(MySQL + MinIO) +- LibreOffice(可选,用于 PDF 导出) diff --git a/backend/config.py b/backend/config.py new file mode 100644 index 0000000..b111198 --- /dev/null +++ b/backend/config.py @@ -0,0 +1,59 @@ +from pydantic_settings import BaseSettings +from pathlib import Path +import os + + +class Settings(BaseSettings): + # 应用 + APP_NAME: str = "AI 文档模板生成系统" + APP_VERSION: str = "1.0.0" + DEBUG: bool = True + + # === 数据库 MySQL === + DB_HOST: str = "localhost" + DB_PORT: int = 3306 + DB_USER: str = "docforge" + DB_PASSWORD: str = "docforge123" + DB_NAME: str = "doc_forge" + @property + def DATABASE_URL(self) -> str: + return f"mysql+asyncmy://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}?charset=utf8mb4" + + # === MinIO 文件存储 === + MINIO_ENDPOINT: str = "localhost:9000" + MINIO_ACCESS_KEY: str = "docforge" + MINIO_SECRET_KEY: str = "docforge123" + MINIO_BUCKET_TEMPLATES: str = "doc-forge-templates" + MINIO_BUCKET_UPLOADS: str = "doc-forge-uploads" + MINIO_BUCKET_OUTPUTS: str = "doc-forge-outputs" + MINIO_USE_SSL: bool = False + + # 本地缓存目录(MinIO 文件的本地临时缓存) + LOCAL_CACHE_DIR: str = "local_cache" + + # 文件上传限制 + MAX_UPLOAD_SIZE: int = 50 * 1024 * 1024 # 50MB + ALLOWED_EXTENSIONS: list = [".docx", ".xlsx", ".xls", ".csv", ".pdf", ".txt", ".md"] + + # 加密(用于 API Key 加密) + ENCRYPTION_KEY: str = "change-this-to-a-32-byte-key-in-production!!" + + # AI 模型默认配置 + AI_REQUEST_TIMEOUT: int = 60 + AI_MAX_RETRIES: int = 3 + AI_MAX_CONCURRENT: int = 5 + AI_GLOBAL_CONCURRENT: int = 10 + + # 服务端口 + HOST: str = "0.0.0.0" + PORT: int = 8000 + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + + +settings = Settings() + +# 创建本地缓存目录 +os.makedirs(settings.LOCAL_CACHE_DIR, exist_ok=True) diff --git a/backend/database.py b/backend/database.py new file mode 100644 index 0000000..364d77c --- /dev/null +++ b/backend/database.py @@ -0,0 +1,28 @@ +from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession +from sqlalchemy.orm import DeclarativeBase +from config import settings + +engine = create_async_engine(settings.DATABASE_URL, echo=settings.DEBUG, pool_size=10, max_overflow=20) +async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) + + +class Base(DeclarativeBase): + pass + + +async def get_db(): + async with async_session() as session: + try: + yield session + finally: + await session.close() + + +async def init_db(): + from models.template import Template + from models.paragraph import Paragraph + from models.ai_model import AiModel + from models.document import Document + from models.generation_log import GenerationLog + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..a8e9186 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,46 @@ +import uvicorn +from contextlib import asynccontextmanager +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from database import init_db, engine +from config import settings +from routers import templates, models, generate, export +from services.minio_client import init_buckets + + +@asynccontextmanager +async def lifespan(app: FastAPI): + await init_db() + await init_buckets() # 初始化 MinIO 存储桶 + yield + await engine.dispose() + + +app = FastAPI(title=settings.APP_NAME, version=settings.APP_VERSION, lifespan=lifespan) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +app.include_router(templates.router, prefix="/api/v1/templates", tags=["模板管理"]) +app.include_router(models.router, prefix="/api/v1/models", tags=["模型管理"]) +app.include_router(generate.router, prefix="/api/v1/generate", tags=["生成管理"]) +app.include_router(export.router, prefix="/api/v1/export", tags=["导出管理"]) + + +@app.get("/") +async def root(): + return {"message": "AI 文档模板生成系统 API", "version": settings.APP_VERSION} + + +@app.get("/health") +async def health(): + return {"status": "ok"} + + +if __name__ == "__main__": + uvicorn.run("main:app", host=settings.HOST, port=settings.PORT, reload=settings.DEBUG) diff --git a/backend/models/__init__.py b/backend/models/__init__.py new file mode 100644 index 0000000..d79adf4 --- /dev/null +++ b/backend/models/__init__.py @@ -0,0 +1,5 @@ +from models.template import Template +from models.paragraph import Paragraph +from models.ai_model import AiModel +from models.document import Document +from models.generation_log import GenerationLog diff --git a/backend/models/ai_model.py b/backend/models/ai_model.py new file mode 100644 index 0000000..bd8bf0d --- /dev/null +++ b/backend/models/ai_model.py @@ -0,0 +1,14 @@ +from sqlalchemy import Column, Integer, String, Text, DateTime, func +from database import Base + +class AiModel(Base): + __tablename__ = "ai_models" + id = Column(Integer, primary_key=True, autoincrement=True) + name = Column(String(255), nullable=False, comment="模型名称") + provider = Column(String(100), default="", comment="供应厂商") + api_format = Column(String(20), default="openai", comment="anthropic/openai") + api_endpoint = Column(String(500), default="", comment="API接口地址") + api_key_encrypted = Column(Text, default="", comment="加密后的API Key") + status = Column(String(20), default="enabled", comment="enabled/disabled") + created_at = Column(DateTime, server_default=func.now()) + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) diff --git a/backend/models/document.py b/backend/models/document.py new file mode 100644 index 0000000..5d83381 --- /dev/null +++ b/backend/models/document.py @@ -0,0 +1,15 @@ +from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, func +from database import Base + +class Document(Base): + __tablename__ = "documents" + id = Column(Integer, primary_key=True, autoincrement=True) + template_id = Column(Integer, ForeignKey("templates.id"), nullable=False) + name = Column(String(255), default="", comment="文档名称") + para_count_done = Column(Integer, default=0, comment="已完成段落数") + para_count_total = Column(Integer, default=0, comment="总段落数") + status = Column(String(20), default="pending", comment="pending/generating/completed/failed/cancelled") + file_path = Column(String(500), default="", comment="生成的文件路径") + error = Column(Text, default="", comment="错误信息") + created_at = Column(DateTime, server_default=func.now()) + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) diff --git a/backend/models/generation_log.py b/backend/models/generation_log.py new file mode 100644 index 0000000..c4f2848 --- /dev/null +++ b/backend/models/generation_log.py @@ -0,0 +1,14 @@ +from sqlalchemy import Column, Integer, String, Text, DateTime, Float, ForeignKey, func +from database import Base + +class GenerationLog(Base): + __tablename__ = "generation_logs" + id = Column(Integer, primary_key=True, autoincrement=True) + document_id = Column(Integer, ForeignKey("documents.id"), nullable=False) + paragraph_id = Column(Integer, ForeignKey("paragraphs.id"), nullable=False) + model_id = Column(Integer, ForeignKey("ai_models.id"), nullable=True) + status = Column(String(20), default="pending", comment="pending/generating/success/failed") + content = Column(Text, default="", comment="生成的内容") + duration = Column(Float, default=0, comment="耗时秒数") + error_msg = Column(Text, default="", comment="错误信息") + created_at = Column(DateTime, server_default=func.now()) diff --git a/backend/models/paragraph.py b/backend/models/paragraph.py new file mode 100644 index 0000000..2c98aec --- /dev/null +++ b/backend/models/paragraph.py @@ -0,0 +1,22 @@ +from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, ForeignKey, func +from database import Base + +class Paragraph(Base): + __tablename__ = "paragraphs" + id = Column(Integer, primary_key=True, autoincrement=True) + template_id = Column(Integer, ForeignKey("templates.id"), nullable=False) + sort_index = Column(Integer, default=0, comment="排序") + title = Column(String(500), default="", comment="段落标题") + content = Column(Text, default="", comment="正文内容/上下文") + style_json = Column(Text, default="{}", comment="段落样式定义JSON") + is_table = Column(Boolean, default=False, comment="是否为表格") + table_json = Column(Text, default="{}", comment="表格结构JSON") + edit_mode = Column(String(20), default="ai", comment="manual/ai") + model_id = Column(Integer, ForeignKey("ai_models.id"), nullable=True, comment="指定模型") + need_prompt = Column(Boolean, default=True, comment="是否需要提示词") + prompt_text = Column(Text, default="", comment="预设提示词") + need_file = Column(Boolean, default=False, comment="是否需要上传参考文件") + file_note = Column(Text, default="", comment="备注说明(传什么文件)") + output_format = Column(String(20), default="text", comment="text/table/mixed/chart") + created_at = Column(DateTime, server_default=func.now()) + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) diff --git a/backend/models/template.py b/backend/models/template.py new file mode 100644 index 0000000..bdbb591 --- /dev/null +++ b/backend/models/template.py @@ -0,0 +1,13 @@ +from sqlalchemy import Column, Integer, String, Text, DateTime, func +from database import Base + +class Template(Base): + __tablename__ = "templates" + id = Column(Integer, primary_key=True, autoincrement=True) + name = Column(String(255), nullable=False, comment="模板名称") + description = Column(Text, default="", comment="描述") + file_path = Column(String(500), nullable=False, comment="原始模板文件路径") + paragraph_count = Column(Integer, default=0, comment="段落数") + status = Column(String(20), default="draft", comment="draft/ready") + created_at = Column(DateTime, server_default=func.now()) + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..066f63b --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,17 @@ +fastapi>=0.110.0 +uvicorn[standard]>=0.29.0 +sqlalchemy>=2.0.25 +asyncmy>=0.2.9 # MySQL async driver +aiomysql>=0.2.0 # MySQL async fallback +cryptography>=42.0.0 +python-docx>=1.1.0 +openpyxl>=3.1.0 +pandas>=2.1.0 +httpx>=0.27.0 +pydantic>=2.5.0 +pydantic-settings>=2.1.0 +python-multipart>=0.0.6 +aiofiles>=23.2.0 +sse-starlette>=2.0.0 +minio>=7.2.0 # MinIO 对象存储 SDK +alembic>=1.13.0 diff --git a/backend/routers/__init__.py b/backend/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/routers/export.py b/backend/routers/export.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/routers/generate.py b/backend/routers/generate.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/routers/models.py b/backend/routers/models.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/routers/templates.py b/backend/routers/templates.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/schemas/__init__.py b/backend/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/schemas/schemas.py b/backend/schemas/schemas.py new file mode 100644 index 0000000..bd07cde --- /dev/null +++ b/backend/schemas/schemas.py @@ -0,0 +1,86 @@ +from pydantic import BaseModel, Field +from typing import Optional, Any +from datetime import datetime + +class Response(BaseModel): + code: int = 0 + data: Any = None + message: str = "ok" + +class PageData(BaseModel): + items: list = [] + total: int = 0 + page: int = 1 + page_size: int = 20 + +# 模板 +class TemplateCreate(BaseModel): + name: str + description: str = "" + +class TemplateOut(BaseModel): + id: int + name: str + description: str = "" + file_path: str = "" + paragraph_count: int = 0 + status: str = "draft" + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + +class ParagraphConfig(BaseModel): + id: int = 0 + sort_index: int = 0 + title: str = "" + edit_mode: str = "ai" + model_id: Optional[int] = None + need_prompt: bool = True + prompt_text: str = "" + need_file: bool = False + file_note: str = "" + output_format: str = "text" + +class TemplateSave(BaseModel): + paragraphs: list[ParagraphConfig] = [] + +# 模型 +class AiModelCreate(BaseModel): + name: str + provider: str = "" + api_format: str = "openai" + api_endpoint: str = "" + api_key: str = "" + status: str = "enabled" + +class AiModelOut(BaseModel): + id: int + name: str + provider: str = "" + api_format: str = "openai" + api_endpoint: str = "" + api_key_preview: str = "" + status: str = "enabled" + created_at: Optional[datetime] = None + +# 生成 +class GenerateTestRequest(BaseModel): + paragraph_id: int + template_id: int + prompt_text: str = "" + model_id: int = 0 + file_paths: list[str] = [] + +class GenerateFullRequest(BaseModel): + template_id: int + file_map: dict[str, str] = {} # paragraph_id -> file_path + +class DocumentOut(BaseModel): + id: int + template_id: int + name: str + para_count_done: int = 0 + para_count_total: int = 0 + status: str = "pending" + file_path: str = "" + error: str = "" + created_at: Optional[datetime] = None diff --git a/backend/services/__init__.py b/backend/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fbb89ef --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,80 @@ +version: "3.8" + +services: + # === MySQL === + mysql: + image: mysql:8.0 + container_name: doc-forge-mysql + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: root123 + MYSQL_DATABASE: doc_forge + MYSQL_USER: docforge + MYSQL_PASSWORD: docforge123 + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + - ./init.sql:/docker-entrypoint-initdb.d/init.sql + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + + # === MinIO(对象存储)=== + minio: + image: minio/minio:latest + container_name: doc-forge-minio + restart: unless-stopped + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: docforge + MINIO_ROOT_PASSWORD: docforge123 + ports: + - "9000:9000" # API + - "9001:9001" # Console + volumes: + - minio_data:/data + healthcheck: + test: ["CMD", "mc", "ready", "local"] + interval: 10s + timeout: 5s + retries: 3 + + # === 后端 === + backend: + build: ./backend + container_name: doc-forge-backend + restart: unless-stopped + depends_on: + mysql: + condition: service_started + minio: + condition: service_healthy + environment: + DB_HOST: mysql + DB_PORT: 3306 + DB_USER: docforge + DB_PASSWORD: docforge123 + DB_NAME: doc_forge + MINIO_ENDPOINT: minio:9000 + MINIO_ACCESS_KEY: docforge + MINIO_SECRET_KEY: docforge123 + ENCRYPTION_KEY: "${ENCRYPTION_KEY}" + ports: + - "8000:8000" + volumes: + - ./backend:/app + - local_cache:/app/local_cache + + # === 前端 === + web: + build: ./web + container_name: doc-forge-web + restart: unless-stopped + depends_on: + - backend + ports: + - "5173:80" + +volumes: + mysql_data: + minio_data: + local_cache: diff --git a/docs/提示词库/提示词模板.md b/docs/提示词库/提示词模板.md new file mode 100644 index 0000000..b586ce1 --- /dev/null +++ b/docs/提示词库/提示词模板.md @@ -0,0 +1,158 @@ +# AI 提示词库 + +本文档包含所有 AI 调用时使用的提示词模板。提示词分为系统级和段落级两层。 + +## 一、系统提示词 + +### 1.1 默认系统提示词(通用) + +``` +你是一个专业的企业文档撰写助手。你的任务是按照给定的段落标题和参考内容, +生成符合中文正式报告风格的段落内容。 + +要求: +1. 语言正式、客观、严谨,使用第三人称 +2. 逻辑清晰,层次分明 +3. 数据准确,引用上传文件中的实际数据 +4. 字数控制在 300-800 字之间 +5. 不要输出标题本身,只输出段落正文内容 +6. 如果正文需要分点描述,使用 1. 2. 3. 编号,不要使用无序列表 + +输出格式必须为 JSON: +{ + "content": [ + {"type": "text", "text": "正文内容..."} + ] +} + +如果需要输出表格,使用: +{ + "content": [ + {"type": "text", "text": "表格说明文字"}, + {"type": "table", "headers": ["列1","列2","列3"], "rows": [["数据1","数据2","数据3"]]} + ] +} +``` + +### 1.2 表格生成专用提示词 + +``` +请根据上传的数据文件,生成以下表格内容: +段落标题:{title} + +要求: +1. 表格列名清晰,数据准确 +2. 只输出表格内容,不要文字说明 +3. 如果有多组数据,优先合并到一张表中 + +输出格式: +{ + "content": [ + {"type": "table", "headers": ["列名1","列名2","..."], "rows": [["值1","值2","..."]]} + ] +} +``` + +### 1.3 报告摘要专用提示词 + +``` +请根据以下参考内容,生成一段简洁的摘要。 + +要求: +1. 概括核心要点,不超过 200 字 +2. 突出关键数据和结论 +3. 使用总分结构 + +输出格式: +{ + "content": [ + {"type": "text", "text": "摘要内容..."} + ] +} +``` + +## 二、段落预设提示词 + +### 2.1 经营指标分析 +``` +请根据上传的财务数据,生成"{title}"章节内容。 +要求包括以下方面: +1. 各核心指标的完成值 +2. 与上期/同期的同比变化 +3. 变化原因分析 +4. 存在的主要风险点 + +参考上下文: +{context} +``` + +### 2.2 成本费用分析 +``` +请根据上传的数据,生成成本费用分析内容。 +要求包括: +1. 各项成本的构成及占比 +2. 同比变化情况及原因 +3. 成本管控措施及成效 + +参考数据: +{context} +``` + +### 2.3 问题总结 +``` +请基于以下数据和背景,分析当前存在的主要问题和风险。 +要求: +1. 问题描述要具体,有数据支撑 +2. 分析问题产生的原因 +3. 指出风险等级和影响范围 + +参考内容: +{context} +``` + +### 2.4 工作措施 +``` +请针对上述问题,生成下一步工作措施。 +要求: +1. 措施具体可执行 +2. 明确责任主体 +3. 设定完成时限或目标值 +4. 措施之间逻辑递进 + +参考内容: +{context} +``` + +## 三、提示词拼接规则 + +### 3.1 最终 prompt 构成 +``` +[系统提示词] +--- +段落标题:{paragraph.title} +编辑方式:{paragraph.edit_mode} +输出格式:{paragraph.output_format} +--- +{paragraph.prompt_text} +--- +参考文件摘要: +{file_summary} +--- +参考上下文: +{paragraph.content} +``` + +### 3.2 参考文件摘要生成规则 +``` +读取上传的 Excel 文件: +1. 提取列名 + 前 10 行数据作为样本 +2. 统计数值列的和/均值/最大最小值 +3. 生成文本摘要 + +Excel 摘要示例: +"文件:财务数据报表.xlsx +包含 3 个工作表: +- Sheet1(使用中):列 [月份, 营业收入, 利润总额, 净利润],共 12 行数据 + 营业收入合计:148.2 亿元,月均 12.35 亿元 + 利润总额合计:15.0 亿元,月均 1.25 亿元 +``` diff --git a/docs/规范与约束/开发规范.md b/docs/规范与约束/开发规范.md new file mode 100644 index 0000000..ec86588 --- /dev/null +++ b/docs/规范与约束/开发规范.md @@ -0,0 +1,105 @@ +# AI 文档模板生成系统 · 开发规范 + +## 一、代码规范 + +### 1.1 Python 后端 +- Python 3.11+,使用类型注解 +- 文件命名:snake_case.py +- 类命名:PascalCase +- 函数/变量:snake_case +- 数据库表:小写复数(templates, paragraphs) +- 异步优先:async/await 贯穿全栈 + +### 1.2 TypeScript 前端 +- TypeScript 5.x,strict 模式 +- 文件命名:PascalCase.vue(组件),camelCase.ts(工具/API) +- 组件命名:多单词 PascalCase +- 变量/函数:camelCase +- 接口命名:I 开头或 PascalCase +- 使用 ` \ No newline at end of file diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..d00f79b --- /dev/null +++ b/web/package.json @@ -0,0 +1,26 @@ +{ + "name": "ai-doc-template-frontend", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.4.0", + "vue-router": "^4.3.0", + "pinia": "^2.1.0", + "axios": "^1.6.0", + "ant-design-vue": "^4.1.0", + "@ant-design/icons-vue": "^7.0.0", + "dayjs": "^1.11.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.0", + "typescript": "^5.3.0", + "vite": "^5.1.0", + "vue-tsc": "^2.0.0" + } +} diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml new file mode 100644 index 0000000..2c9b242 --- /dev/null +++ b/web/pnpm-lock.yaml @@ -0,0 +1,1384 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@ant-design/icons-vue': + specifier: ^7.0.0 + version: 7.0.1(vue@3.5.39(typescript@5.9.3)) + ant-design-vue: + specifier: ^4.1.0 + version: 4.2.6(vue@3.5.39(typescript@5.9.3)) + axios: + specifier: ^1.6.0 + version: 1.18.1 + dayjs: + specifier: ^1.11.0 + version: 1.11.21 + pinia: + specifier: ^2.1.0 + version: 2.3.1(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3)) + vue: + specifier: ^3.4.0 + version: 3.5.39(typescript@5.9.3) + vue-router: + specifier: ^4.3.0 + version: 4.6.4(vue@3.5.39(typescript@5.9.3)) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^5.0.0 + version: 5.2.4(vite@5.4.21)(vue@3.5.39(typescript@5.9.3)) + typescript: + specifier: ^5.3.0 + version: 5.9.3 + vite: + specifier: ^5.1.0 + version: 5.4.21 + vue-tsc: + specifier: ^2.0.0 + version: 2.2.12(typescript@5.9.3) + +packages: + + '@ant-design/colors@6.0.0': + resolution: {integrity: sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==} + + '@ant-design/icons-svg@4.5.0': + resolution: {integrity: sha512-1BTUFyKPTBZ53MuTP8s0k5SFEXL7o3VHEOwLgzaoWKwnBeqIcqUtVshc4SKzhI6uACfqhJqBwBUE9FsWR3uULA==} + + '@ant-design/icons-vue@7.0.1': + resolution: {integrity: sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==} + peerDependencies: + vue: '>=3.0.3' + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@ctrl/tinycolor@3.6.1': + resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} + engines: {node: '>=10'} + + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@emotion/unitless@0.8.1': + resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + cpu: [x64] + os: [win32] + + '@simonwep/pickr@1.8.2': + resolution: {integrity: sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@vitejs/plugin-vue@5.2.4': + resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.15': + resolution: {integrity: sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==} + + '@volar/source-map@2.4.15': + resolution: {integrity: sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==} + + '@volar/typescript@2.4.15': + resolution: {integrity: sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==} + + '@vue/compiler-core@3.5.39': + resolution: {integrity: sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==} + + '@vue/compiler-dom@3.5.39': + resolution: {integrity: sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==} + + '@vue/compiler-sfc@3.5.39': + resolution: {integrity: sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==} + + '@vue/compiler-ssr@3.5.39': + resolution: {integrity: sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/language-core@2.2.12': + resolution: {integrity: sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.39': + resolution: {integrity: sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==} + + '@vue/runtime-core@3.5.39': + resolution: {integrity: sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==} + + '@vue/runtime-dom@3.5.39': + resolution: {integrity: sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==} + + '@vue/server-renderer@3.5.39': + resolution: {integrity: sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==} + peerDependencies: + vue: 3.5.39 + + '@vue/shared@3.5.39': + resolution: {integrity: sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + alien-signals@1.0.13: + resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==} + + ant-design-vue@4.2.6: + resolution: {integrity: sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==} + engines: {node: '>=12.22.0'} + peerDependencies: + vue: '>=3.2.0' + + array-tree-filter@2.1.0: + resolution: {integrity: sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.18.1: + resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@2.1.1: + resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + compute-scroll-into-view@1.0.20: + resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + + core-js@3.49.0: + resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dom-align@1.12.4: + resolution: {integrity: sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==} + + dom-scroll-into-view@2.0.1: + resolution: {integrity: sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + is-plain-object@3.0.1: + resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==} + engines: {node: '>=0.10.0'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.15: + resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanopop@2.4.2: + resolution: {integrity: sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + pinia@2.3.1: + resolution: {integrity: sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==} + peerDependencies: + typescript: '>=4.4.4' + vue: ^2.7.0 || ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + postcss@8.5.16: + resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} + engines: {node: ^10 || ^12 || >=14} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scroll-into-view-if-needed@2.2.31: + resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + + shallow-equal@1.2.1: + resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + + throttle-debounce@5.0.2: + resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} + engines: {node: '>=12.22'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + 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 + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-router@4.6.4: + resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==} + peerDependencies: + vue: ^3.5.0 + + vue-tsc@2.2.12: + resolution: {integrity: sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue-types@3.0.2: + resolution: {integrity: sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==} + engines: {node: '>=10.15.0'} + peerDependencies: + vue: ^3.0.0 + + vue@3.5.39: + resolution: {integrity: sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + warning@4.0.3: + resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} + +snapshots: + + '@ant-design/colors@6.0.0': + dependencies: + '@ctrl/tinycolor': 3.6.1 + + '@ant-design/icons-svg@4.5.0': {} + + '@ant-design/icons-vue@7.0.1(vue@3.5.39(typescript@5.9.3))': + dependencies: + '@ant-design/colors': 6.0.0 + '@ant-design/icons-svg': 4.5.0 + vue: 3.5.39(typescript@5.9.3) + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/runtime@7.29.7': {} + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@ctrl/tinycolor@3.6.1': {} + + '@emotion/hash@0.9.2': {} + + '@emotion/unitless@0.8.1': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@rollup/rollup-android-arm-eabi@4.62.2': + optional: true + + '@rollup/rollup-android-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-x64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.2': + optional: true + + '@simonwep/pickr@1.8.2': + dependencies: + core-js: 3.49.0 + nanopop: 2.4.2 + + '@types/estree@1.0.9': {} + + '@vitejs/plugin-vue@5.2.4(vite@5.4.21)(vue@3.5.39(typescript@5.9.3))': + dependencies: + vite: 5.4.21 + vue: 3.5.39(typescript@5.9.3) + + '@volar/language-core@2.4.15': + dependencies: + '@volar/source-map': 2.4.15 + + '@volar/source-map@2.4.15': {} + + '@volar/typescript@2.4.15': + dependencies: + '@volar/language-core': 2.4.15 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/compiler-core@3.5.39': + dependencies: + '@babel/parser': 7.29.7 + '@vue/shared': 3.5.39 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.39': + dependencies: + '@vue/compiler-core': 3.5.39 + '@vue/shared': 3.5.39 + + '@vue/compiler-sfc@3.5.39': + dependencies: + '@babel/parser': 7.29.7 + '@vue/compiler-core': 3.5.39 + '@vue/compiler-dom': 3.5.39 + '@vue/compiler-ssr': 3.5.39 + '@vue/shared': 3.5.39 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.16 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.39': + dependencies: + '@vue/compiler-dom': 3.5.39 + '@vue/shared': 3.5.39 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/devtools-api@6.6.4': {} + + '@vue/language-core@2.2.12(typescript@5.9.3)': + dependencies: + '@volar/language-core': 2.4.15 + '@vue/compiler-dom': 3.5.39 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.39 + alien-signals: 1.0.13 + minimatch: 9.0.9 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.9.3 + + '@vue/reactivity@3.5.39': + dependencies: + '@vue/shared': 3.5.39 + + '@vue/runtime-core@3.5.39': + dependencies: + '@vue/reactivity': 3.5.39 + '@vue/shared': 3.5.39 + + '@vue/runtime-dom@3.5.39': + dependencies: + '@vue/reactivity': 3.5.39 + '@vue/runtime-core': 3.5.39 + '@vue/shared': 3.5.39 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.39(vue@3.5.39(typescript@5.9.3))': + dependencies: + '@vue/compiler-ssr': 3.5.39 + '@vue/shared': 3.5.39 + vue: 3.5.39(typescript@5.9.3) + + '@vue/shared@3.5.39': {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + alien-signals@1.0.13: {} + + ant-design-vue@4.2.6(vue@3.5.39(typescript@5.9.3)): + dependencies: + '@ant-design/colors': 6.0.0 + '@ant-design/icons-vue': 7.0.1(vue@3.5.39(typescript@5.9.3)) + '@babel/runtime': 7.29.7 + '@ctrl/tinycolor': 3.6.1 + '@emotion/hash': 0.9.2 + '@emotion/unitless': 0.8.1 + '@simonwep/pickr': 1.8.2 + array-tree-filter: 2.1.0 + async-validator: 4.2.5 + csstype: 3.2.3 + dayjs: 1.11.21 + dom-align: 1.12.4 + dom-scroll-into-view: 2.0.1 + lodash: 4.18.1 + lodash-es: 4.18.1 + resize-observer-polyfill: 1.5.1 + scroll-into-view-if-needed: 2.2.31 + shallow-equal: 1.2.1 + stylis: 4.4.0 + throttle-debounce: 5.0.2 + vue: 3.5.39(typescript@5.9.3) + vue-types: 3.0.2(vue@3.5.39(typescript@5.9.3)) + warning: 4.0.3 + + array-tree-filter@2.1.0: {} + + async-validator@4.2.5: {} + + asynckit@0.4.0: {} + + axios@1.18.1: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + + balanced-match@1.0.2: {} + + brace-expansion@2.1.1: + dependencies: + balanced-match: 1.0.2 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + compute-scroll-into-view@1.0.20: {} + + core-js@3.49.0: {} + + csstype@3.2.3: {} + + dayjs@1.11.21: {} + + de-indent@1.0.2: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + delayed-stream@1.0.0: {} + + dom-align@1.12.4: {} + + dom-scroll-into-view@2.0.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + entities@7.0.1: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + esbuild@0.21.5: + 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 + + estree-walker@2.0.2: {} + + follow-redirects@1.16.0: {} + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + is-plain-object@3.0.1: {} + + js-tokens@4.0.0: {} + + lodash-es@4.18.1: {} + + lodash@4.18.1: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.1 + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.15: {} + + nanopop@2.4.2: {} + + path-browserify@1.0.1: {} + + picocolors@1.1.1: {} + + pinia@2.3.1(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.39(typescript@5.9.3) + vue-demi: 0.14.10(vue@3.5.39(typescript@5.9.3)) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@vue/composition-api' + + postcss@8.5.16: + dependencies: + nanoid: 3.3.15 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + proxy-from-env@2.1.0: {} + + resize-observer-polyfill@1.5.1: {} + + rollup@4.62.2: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 + fsevents: 2.3.3 + + scroll-into-view-if-needed@2.2.31: + dependencies: + compute-scroll-into-view: 1.0.20 + + shallow-equal@1.2.1: {} + + source-map-js@1.2.1: {} + + stylis@4.4.0: {} + + throttle-debounce@5.0.2: {} + + typescript@5.9.3: {} + + vite@5.4.21: + dependencies: + esbuild: 0.21.5 + postcss: 8.5.16 + rollup: 4.62.2 + optionalDependencies: + fsevents: 2.3.3 + + vscode-uri@3.1.0: {} + + vue-demi@0.14.10(vue@3.5.39(typescript@5.9.3)): + dependencies: + vue: 3.5.39(typescript@5.9.3) + + vue-router@4.6.4(vue@3.5.39(typescript@5.9.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.39(typescript@5.9.3) + + vue-tsc@2.2.12(typescript@5.9.3): + dependencies: + '@volar/typescript': 2.4.15 + '@vue/language-core': 2.2.12(typescript@5.9.3) + typescript: 5.9.3 + + vue-types@3.0.2(vue@3.5.39(typescript@5.9.3)): + dependencies: + is-plain-object: 3.0.1 + vue: 3.5.39(typescript@5.9.3) + + vue@3.5.39(typescript@5.9.3): + dependencies: + '@vue/compiler-dom': 3.5.39 + '@vue/compiler-sfc': 3.5.39 + '@vue/runtime-dom': 3.5.39 + '@vue/server-renderer': 3.5.39(vue@3.5.39(typescript@5.9.3)) + '@vue/shared': 3.5.39 + optionalDependencies: + typescript: 5.9.3 + + warning@4.0.3: + dependencies: + loose-envify: 1.4.0 diff --git a/web/src/App.vue b/web/src/App.vue new file mode 100644 index 0000000..0b0f97f --- /dev/null +++ b/web/src/App.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/web/src/api/generate.ts b/web/src/api/generate.ts new file mode 100644 index 0000000..c4c31f4 --- /dev/null +++ b/web/src/api/generate.ts @@ -0,0 +1,13 @@ +import http from './index' + +export const generateApi = { + test: (data: any) => http.post('/generate/test', data), + full: (data: any) => http.post('/generate/full', data), + progress: (id: number) => `/api/v1/generate/progress/${id}`, + cancel: (id: number) => http.post(`/generate/cancel/${id}`), + documents: (params?: any) => http.get('/generate/documents', { params }), + getDocument: (id: number) => http.get(`/generate/documents/${id}`), + deleteDocument: (id: number) => http.delete(`/generate/documents/${id}`), + exportDocx: (id: number) => `/api/v1/export/${id}/docx`, + exportPdf: (id: number) => `/api/v1/export/${id}/pdf`, +} diff --git a/web/src/api/index.ts b/web/src/api/index.ts new file mode 100644 index 0000000..d3cfd41 --- /dev/null +++ b/web/src/api/index.ts @@ -0,0 +1,14 @@ +import axios from 'axios' + +const http = axios.create({ baseURL: '/api/v1', timeout: 30000 }) + +http.interceptors.response.use( + res => res.data, + err => { + const msg = err.response?.data?.message || err.message || '网络错误' + console.error('[API Error]', msg) + return Promise.reject({ code: -1, message: msg }) + } +) + +export default http diff --git a/web/src/api/model.ts b/web/src/api/model.ts new file mode 100644 index 0000000..9f8bfa9 --- /dev/null +++ b/web/src/api/model.ts @@ -0,0 +1,9 @@ +import http from './index' + +export const modelApi = { + list: () => http.get('/models'), + create: (data: any) => http.post('/models', data), + update: (id: number, data: any) => http.put(`/models/${id}`, data), + delete: (id: number) => http.delete(`/models/${id}`), + test: (id: number) => http.post(`/models/${id}/test`), +} diff --git a/web/src/api/template.ts b/web/src/api/template.ts new file mode 100644 index 0000000..f05c173 --- /dev/null +++ b/web/src/api/template.ts @@ -0,0 +1,9 @@ +import http from './index' + +export const templateApi = { + list: (params?: any) => http.get('/templates', { params }), + get: (id: number) => http.get(`/templates/${id}`), + upload: (formData: FormData) => http.post('/templates/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } }), + saveParagraphs: (id: number, data: any) => http.put(`/templates/${id}/paragraphs`, data), + delete: (id: number) => http.delete(`/templates/${id}`), +} diff --git a/web/src/components/DocPreview.vue b/web/src/components/DocPreview.vue new file mode 100644 index 0000000..770b666 --- /dev/null +++ b/web/src/components/DocPreview.vue @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/web/src/components/FileUploader.vue b/web/src/components/FileUploader.vue new file mode 100644 index 0000000..3e4445c --- /dev/null +++ b/web/src/components/FileUploader.vue @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/web/src/components/ModelModal.vue b/web/src/components/ModelModal.vue new file mode 100644 index 0000000..b3473a5 --- /dev/null +++ b/web/src/components/ModelModal.vue @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/web/src/components/ParagraphConfig.vue b/web/src/components/ParagraphConfig.vue new file mode 100644 index 0000000..8d9c8aa --- /dev/null +++ b/web/src/components/ParagraphConfig.vue @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/web/src/components/ParagraphList.vue b/web/src/components/ParagraphList.vue new file mode 100644 index 0000000..e8612c1 --- /dev/null +++ b/web/src/components/ParagraphList.vue @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/web/src/components/TestModal.vue b/web/src/components/TestModal.vue new file mode 100644 index 0000000..b69c434 --- /dev/null +++ b/web/src/components/TestModal.vue @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/web/src/env.d.ts b/web/src/env.d.ts new file mode 100644 index 0000000..2e2e29f --- /dev/null +++ b/web/src/env.d.ts @@ -0,0 +1,2 @@ +/// +declare module "*.vue" { import type { DefineComponent } from "vue"; const comp: DefineComponent<{}, {}, any>; export default comp; } diff --git a/web/src/main.ts b/web/src/main.ts new file mode 100644 index 0000000..d7bf28e --- /dev/null +++ b/web/src/main.ts @@ -0,0 +1,12 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import Antd from 'ant-design-vue' +import 'ant-design-vue/dist/reset.css' +import App from './App.vue' +import router from './router' + +const app = createApp(App) +app.use(createPinia()) +app.use(router) +app.use(Antd) +app.mount('#app') diff --git a/web/src/router/index.ts b/web/src/router/index.ts new file mode 100644 index 0000000..26d793d --- /dev/null +++ b/web/src/router/index.ts @@ -0,0 +1,14 @@ +import { createRouter, createWebHistory } from 'vue-router' + +const routes = [ + { path: '/', redirect: '/templates' }, + { path: '/templates', name: 'TemplateList', component: () => import('@/views/TemplateList.vue') }, + { path: '/templates/:id/edit', name: 'TemplateEditor', component: () => import('@/views/TemplateEditor.vue') }, + { path: '/models', name: 'ModelManage', component: () => import('@/views/ModelManage.vue') }, + { path: '/generate', name: 'GeneratePage', component: () => import('@/views/GeneratePage.vue') }, + { path: '/history', name: 'HistoryPage', component: () => import('@/views/HistoryPage.vue') }, + { path: '/preview/:id', name: 'PreviewEdit', component: () => import('@/views/PreviewEdit.vue') }, +] + +const router = createRouter({ history: createWebHistory(), routes }) +export default router diff --git a/web/src/stores/document.ts b/web/src/stores/document.ts new file mode 100644 index 0000000..11f3f01 --- /dev/null +++ b/web/src/stores/document.ts @@ -0,0 +1,17 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { generateApi } from '@/api/generate' +import type { Document } from '@/types' + +export const useDocumentStore = defineStore('document', () => { + const documents = ref([]) + const currentDoc = ref(null) + const loading = ref(false) + + async function fetchList(params?: any) { loading.value = true; try { const r: any = await generateApi.documents(params); documents.value = r.data?.items || r.data || [] } finally { loading.value = false } } + async function generateFull(data: any) { const r: any = await generateApi.full(data); return r.data } + async function cancel(id: number) { await generateApi.cancel(id) } + async function removeDoc(id: number) { await generateApi.deleteDocument(id); await fetchList() } + + return { documents, currentDoc, loading, fetchList, generateFull, cancel, removeDoc } +}) diff --git a/web/src/stores/model.ts b/web/src/stores/model.ts new file mode 100644 index 0000000..59e4231 --- /dev/null +++ b/web/src/stores/model.ts @@ -0,0 +1,17 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { modelApi } from '@/api/model' +import type { AiModel } from '@/types' + +export const useModelStore = defineStore('model', () => { + const models = ref([]) + const loading = ref(false) + + async function fetchList() { loading.value = true; try { const r: any = await modelApi.list(); models.value = r.data || [] } finally { loading.value = false } } + async function create(data: any) { await modelApi.create(data); await fetchList() } + async function update(id: number, data: any) { await modelApi.update(id, data); await fetchList() } + async function remove(id: number) { await modelApi.delete(id); await fetchList() } + async function test(id: number) { return await modelApi.test(id) } + + return { models, loading, fetchList, create, update, remove, test } +}) diff --git a/web/src/stores/template.ts b/web/src/stores/template.ts new file mode 100644 index 0000000..36db4d6 --- /dev/null +++ b/web/src/stores/template.ts @@ -0,0 +1,19 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { templateApi } from '@/api/template' +import type { Template, Paragraph } from '@/types' + +export const useTemplateStore = defineStore('template', () => { + const templates = ref([]) + const currentTemplate = ref