commit 3af47d48cfd8c2e46faa0641b8f0c44ba7f3ce66 Author: ViperEkura <3081035982@qq.com> Date: Tue Mar 24 12:12:03 2026 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..576cf0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Ignore everything +* + +# # Allow directories +!*/ + +# Allow docs and settings +!/docs/*.md +!/README.md +!.gitignore + +# Allow backend source +!*.py +!*.toml + +# Allow frontend source +!frontend/ +!frontend/package.json +!frontend/package-lock.json +!frontend/*.js +!frontend/*.html +!frontend/src/ +!frontend/src/**/*.js +!frontend/src/**/*.vue +!frontend/src/**/*.css +!frontend/public/ +!frontend/public/** \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..33dbff2 --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +# Nano Claw + +基于 GLM 大语言模型的对话应用,支持流式回复和思维链。 + + +## 快速开始 + +### 1. 克隆并安装后端 + +```bash +cd Nano-Claw + +# 创建虚拟环境 +python -m venv .venv +source .venv/bin/activate # Windows: .venv\Scripts\activate + +# 安装依赖 +pip install -e . +``` + +### 2. 配置 + +创建并编辑 `config.yml`,填入你的信息: + +```yaml +# GLM API +api_key: your-api-key-here +api_url: https://open.bigmodel.cn/api/paas/v4/chat/completions + +# MySQL +db_host: localhost +db_port: 3306 +db_user: root +db_password: "" +db_name: glm_chat +``` + +### 3. 初始化数据库 + +```bash +mysql -u root -p -e "CREATE DATABASE glm_chat CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" + +``` + +### 4. 启动后端 + +```bash +flask --app backend run --port 5000 +``` + +### 5. 启动前端 + +```bash +cd frontend +npm install +npm run dev +``` + +打开 http://localhost:3000 即可使用。 + +## 项目结构 + +``` +├── backend/ # Flask 后端 +│ ├── __init__.py +│ ├── models.py # 数据模型 +│ └── routes.py # API 路由 +├── frontend/ # Vue 3 前端 +│ └── src/ +│ ├── api/ # API 请求层 +│ └── components/ # UI 组件 +├── docs/ # 文档 +├── config.yml.example +└── pyproject.toml +``` + +## API 概览 + +| 方法 | 路径 | 说明 | +|------|------|------| +| POST | `/api/conversations` | 创建会话 | +| GET | `/api/conversations` | 会话列表 | +| PATCH | `/api/conversations/:id` | 更新会话 | +| DELETE | `/api/conversations/:id` | 删除会话 | +| GET | `/api/conversations/:id/messages` | 消息列表 | +| POST | `/api/conversations/:id/messages` | 发送消息(支持 SSE 流式) | +| DELETE | `/api/conversations/:id/messages/:mid` | 删除消息 | + +详细 API 文档见 [docs/design.md](docs/design.md)。 diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 0000000..564dcca --- /dev/null +++ b/backend/__init__.py @@ -0,0 +1,36 @@ +import os +import yaml +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from pathlib import Path + +db = SQLAlchemy() +CONFIG_PATH = Path(__file__).parent.parent / "config.yml" + + +def load_config(): + with open(CONFIG_PATH, encoding="utf-8") as f: + return yaml.safe_load(f) + + +def create_app(): + cfg = load_config() + + app = Flask(__name__) + app.config["SQLALCHEMY_DATABASE_URI"] = ( + f"mysql+pymysql://{cfg['db_user']}:{cfg['db_password']}" + f"@{cfg.get('db_host', 'localhost')}:{cfg.get('db_port', 3306)}/{cfg['db_name']}" + f"?charset=utf8mb4" + ) + app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False + + db.init_app(app) + + from .models import User, Conversation, Message + from .routes import register_routes + register_routes(app) + + with app.app_context(): + db.create_all() + + return app diff --git a/backend/models.py b/backend/models.py new file mode 100644 index 0000000..693322a --- /dev/null +++ b/backend/models.py @@ -0,0 +1,46 @@ +from datetime import datetime +from . import db + + +class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.BigInteger, primary_key=True, autoincrement=True) + username = db.Column(db.String(50), unique=True, nullable=False) + password = db.Column(db.String(255), nullable=False) + phone = db.Column(db.String(20)) + + conversations = db.relationship("Conversation", backref="user", lazy="dynamic", + cascade="all, delete-orphan", + order_by="Conversation.updated_at.desc()") + + +class Conversation(db.Model): + __tablename__ = "conversations" + + id = db.Column(db.String(64), primary_key=True) + user_id = db.Column(db.BigInteger, db.ForeignKey("users.id"), nullable=False) + title = db.Column(db.String(255), nullable=False, default="") + model = db.Column(db.String(64), nullable=False, default="glm-5") + system_prompt = db.Column(db.Text, default="") + temperature = db.Column(db.Float, nullable=False, default=1.0) + max_tokens = db.Column(db.Integer, nullable=False, default=65536) + thinking_enabled = db.Column(db.Boolean, default=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + messages = db.relationship("Message", backref="conversation", lazy="dynamic", + cascade="all, delete-orphan", + order_by="Message.created_at.asc()") + + +class Message(db.Model): + __tablename__ = "messages" + + id = db.Column(db.String(64), primary_key=True) + conversation_id = db.Column(db.String(64), db.ForeignKey("conversations.id"), nullable=False) + role = db.Column(db.String(16), nullable=False) + content = db.Column(db.Text, default="") + token_count = db.Column(db.Integer, default=0) + thinking_content = db.Column(db.Text, default="") + created_at = db.Column(db.DateTime, default=datetime.utcnow) diff --git a/backend/routes.py b/backend/routes.py new file mode 100644 index 0000000..6ba3d35 --- /dev/null +++ b/backend/routes.py @@ -0,0 +1,271 @@ +import uuid +import json +import os +import requests +from datetime import datetime +from flask import request, jsonify, Response, Blueprint +from . import db +from .models import Conversation, Message, User +from . import load_config + +bp = Blueprint("api", __name__) + +cfg = load_config() +GLM_API_URL = cfg.get("api_url") +GLM_API_KEY = cfg["api_key"] + + +# -- Helpers ---------------------------------------------- + +def get_or_create_default_user(): + user = User.query.filter_by(username="default").first() + if not user: + user = User(username="default", password="") + db.session.add(user) + db.session.commit() + return user + + +def ok(data=None, message=None): + body = {"code": 0} + if data is not None: + body["data"] = data + if message is not None: + body["message"] = message + return jsonify(body) + + +def err(code, message): + return jsonify({"code": code, "message": message}), code + + +def to_dict(inst, **extra): + d = {c.name: getattr(inst, c.name) for c in inst.__table__.columns} + for k in ("created_at", "updated_at"): + if k in d and hasattr(d[k], "strftime"): + d[k] = d[k].strftime("%Y-%m-%dT%H:%M:%SZ") + d.update(extra) + return d + + +def build_glm_messages(conv): + msgs = [] + if conv.system_prompt: + msgs.append({"role": "system", "content": conv.system_prompt}) + for m in conv.messages: + msgs.append({"role": m.role, "content": m.content}) + return msgs + + +# -- Conversation CRUD ------------------------------------ + +@bp.route("/api/conversations", methods=["GET", "POST"]) +def conversation_list(): + if request.method == "POST": + d = request.json or {} + user = get_or_create_default_user() + conv = Conversation( + id=str(uuid.uuid4()), + user_id=user.id, + title=d.get("title", ""), + model=d.get("model", "glm-5"), + system_prompt=d.get("system_prompt", ""), + temperature=d.get("temperature", 1.0), + max_tokens=d.get("max_tokens", 65536), + thinking_enabled=d.get("thinking_enabled", False), + ) + db.session.add(conv) + db.session.commit() + return ok(to_dict(conv)) + + cursor = request.args.get("cursor") + limit = min(int(request.args.get("limit", 20)), 100) + user = get_or_create_default_user() + q = Conversation.query.filter_by(user_id=user.id) + if cursor: + q = q.filter(Conversation.updated_at < ( + db.session.query(Conversation.updated_at).filter_by(id=cursor).scalar() or datetime.utcnow)) + rows = q.order_by(Conversation.updated_at.desc()).limit(limit + 1).all() + + items = [to_dict(r, message_count=r.messages.count()) for r in rows[:limit]] + return ok({ + "items": items, + "next_cursor": items[-1]["id"] if len(rows) > limit else None, + "has_more": len(rows) > limit, + }) + + +@bp.route("/api/conversations/", methods=["GET", "PATCH", "DELETE"]) +def conversation_detail(conv_id): + conv = db.session.get(Conversation, conv_id) + if not conv: + return err(404, "conversation not found") + + if request.method == "GET": + return ok(to_dict(conv)) + + if request.method == "DELETE": + db.session.delete(conv) + db.session.commit() + return ok(message="deleted") + + d = request.json or {} + for k in ("title", "model", "system_prompt", "temperature", "max_tokens", "thinking_enabled"): + if k in d: + setattr(conv, k, d[k]) + db.session.commit() + return ok(to_dict(conv)) + + +# -- Messages --------------------------------------------- + +@bp.route("/api/conversations//messages", methods=["GET", "POST"]) +def message_list(conv_id): + conv = db.session.get(Conversation, conv_id) + if not conv: + return err(404, "conversation not found") + + if request.method == "GET": + cursor = request.args.get("cursor") + limit = min(int(request.args.get("limit", 50)), 100) + q = Message.query.filter_by(conversation_id=conv_id) + if cursor: + q = q.filter(Message.created_at < ( + db.session.query(Message.created_at).filter_by(id=cursor).scalar() or datetime.utcnow)) + rows = q.order_by(Message.created_at.asc()).limit(limit + 1).all() + + items = [to_dict(r) for r in rows[:limit]] + return ok({ + "items": items, + "next_cursor": items[-1]["id"] if len(rows) > limit else None, + "has_more": len(rows) > limit, + }) + + d = request.json or {} + content = (d.get("content") or "").strip() + if not content: + return err(400, "content is required") + + user_msg = Message(id=str(uuid.uuid4()), conversation_id=conv_id, role="user", content=content) + db.session.add(user_msg) + db.session.commit() + + if d.get("stream", False): + return _stream_response(conv) + + return _sync_response(conv) + + +@bp.route("/api/conversations//messages/", methods=["DELETE"]) +def delete_message(conv_id, msg_id): + conv = db.session.get(Conversation, conv_id) + if not conv: + return err(404, "conversation not found") + msg = db.session.get(Message, msg_id) + if not msg or msg.conversation_id != conv_id: + return err(404, "message not found") + db.session.delete(msg) + db.session.commit() + return ok(message="deleted") + + +# -- Chat Completion ---------------------------------- + +def _call_glm(conv, stream=False): + body = { + "model": conv.model, + "messages": build_glm_messages(conv), + "max_tokens": conv.max_tokens, + "temperature": conv.temperature, + } + if conv.thinking_enabled: + body["thinking"] = {"type": "enabled"} + if stream: + body["stream"] = True + return requests.post( + GLM_API_URL, + headers={"Content-Type": "application/json", "Authorization": f"Bearer {GLM_API_KEY}"}, + json=body, stream=stream, timeout=120, + ) + + +def _sync_response(conv): + try: + resp = _call_glm(conv) + resp.raise_for_status() + result = resp.json() + except Exception as e: + return err(500, f"upstream error: {e}") + + choice = result["choices"][0] + usage = result.get("usage", {}) + msg = Message( + id=str(uuid.uuid4()), conversation_id=conv.id, role="assistant", + content=choice["message"]["content"], + token_count=usage.get("completion_tokens", 0), + thinking_content=choice["message"].get("reasoning_content", ""), + ) + db.session.add(msg) + db.session.commit() + + return ok({ + "message": to_dict(msg, thinking_content=msg.thinking_content or None), + "usage": {"prompt_tokens": usage.get("prompt_tokens", 0), + "completion_tokens": usage.get("completion_tokens", 0), + "total_tokens": usage.get("total_tokens", 0)}, + }) + + +def _stream_response(conv): + def generate(): + full_content = "" + full_thinking = "" + token_count = 0 + msg_id = str(uuid.uuid4()) + + try: + resp = _call_glm(conv, stream=True) + resp.raise_for_status() + for line in resp.iter_lines(): + if not line: + continue + line = line.decode("utf-8") + if not line.startswith("data: "): + continue + data_str = line[6:] + if data_str == "[DONE]": + break + try: + chunk = json.loads(data_str) + except json.JSONDecodeError: + continue + delta = chunk["choices"][0].get("delta", {}) + reasoning = delta.get("reasoning_content", "") + text = delta.get("content", "") + if reasoning: + full_thinking += reasoning + yield f"event: thinking\ndata: {json.dumps({'content': reasoning}, ensure_ascii=False)}\n\n" + if text: + full_content += text + yield f"event: message\ndata: {json.dumps({'content': text}, ensure_ascii=False)}\n\n" + usage = chunk.get("usage", {}) + if usage: + token_count = usage.get("completion_tokens", 0) + except Exception as e: + yield f"event: error\ndata: {json.dumps({'content': str(e)}, ensure_ascii=False)}\n\n" + return + + msg = Message( + id=msg_id, conversation_id=conv.id, role="assistant", + content=full_content, token_count=token_count, thinking_content=full_thinking, + ) + db.session.add(msg) + db.session.commit() + yield f"event: done\ndata: {json.dumps({'message_id': msg_id, 'token_count': token_count})}\n\n" + + return Response(generate(), mimetype="text/event-stream", + headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"}) + + +def register_routes(app): + app.register_blueprint(bp) diff --git a/backend/run.py b/backend/run.py new file mode 100644 index 0000000..2d1989c --- /dev/null +++ b/backend/run.py @@ -0,0 +1,6 @@ +from backend import create_app + +app = create_app() + +if __name__ == "__main__": + app.run(debug=True, port=5000) diff --git a/docs/Design.md b/docs/Design.md new file mode 100644 index 0000000..236f706 --- /dev/null +++ b/docs/Design.md @@ -0,0 +1,351 @@ +# 对话系统后端 API 设计 + +## API 总览 + +### 会话管理 + +| 方法 | 路径 | 说明 | +|------|------|------| +| `POST` | `/api/conversations` | 创建会话 | +| `GET` | `/api/conversations` | 获取会话列表 | +| `GET` | `/api/conversations/:id` | 获取会话详情 | +| `PATCH` | `/api/conversations/:id` | 更新会话 | +| `DELETE` | `/api/conversations/:id` | 删除会话 | + +### 消息管理 + +| 方法 | 路径 | 说明 | +|------|------|------| +| `GET` | `/api/conversations/:id/messages` | 获取消息列表 | +| `POST` | `/api/conversations/:id/messages` | 发送消息(对话补全,支持 `stream` 流式) | +| `DELETE` | `/api/conversations/:id/messages/:message_id` | 删除消息 | + +--- + +## API 接口 + +### 1. 会话管理 + +#### 创建会话 + +``` +POST /api/conversations +``` + +**请求体:** + +```json +{ + "title": "新对话", + "model": "glm-5", + "system_prompt": "你是一个有帮助的助手", + "temperature": 1.0, + "max_tokens": 65536, + "thinking_enabled": false +} +``` + +**响应:** + +```json +{ + "code": 0, + "data": { + "id": "conv_abc123", + "title": "新对话", + "model": "glm-5", + "system_prompt": "你是一个有帮助的助手", + "temperature": 1.0, + "max_tokens": 65536, + "thinking_enabled": false, + "created_at": "2026-03-24T10:00:00Z", + "updated_at": "2026-03-24T10:00:00Z" + } +} +``` + +#### 获取会话列表 + +``` +GET /api/conversations?cursor=conv_abc123&limit=20 +``` + +| 参数 | 类型 | 说明 | +| -------- | ------- | ----------------- | +| `cursor` | string | 分页游标,为空取首页 | +| `limit` | integer | 每页数量,默认 20,最大 100 | + +**响应:** + +```json +{ + "code": 0, + "data": { + "items": [ + { + "id": "conv_abc123", + "title": "新对话", + "model": "glm-5", + "created_at": "2026-03-24T10:00:00Z", + "updated_at": "2026-03-24T10:05:00Z", + "message_count": 6 + } + ], + "next_cursor": "conv_def456", + "has_more": true + } +} +``` + +#### 获取会话详情 + +``` +GET /api/conversations/:id +``` + +**响应:** + +```json +{ + "code": 0, + "data": { + "id": "conv_abc123", + "title": "新对话", + "model": "glm-5", + "system_prompt": "你是一个有帮助的助手", + "temperature": 1.0, + "max_tokens": 65536, + "thinking_enabled": false, + "created_at": "2026-03-24T10:00:00Z", + "updated_at": "2026-03-24T10:05:00Z" + } +} +``` + +#### 更新会话 + +``` +PATCH /api/conversations/:id +``` + +**请求体(仅传需更新的字段):** + +```json +{ + "title": "修改后的标题", + "system_prompt": "新的系统提示词", + "temperature": 0.8 +} +``` + +**响应:** 同获取会话详情 + +#### 删除会话 + +``` +DELETE /api/conversations/:id +``` + +**响应:** + +```json +{ + "code": 0, + "message": "deleted" +} +``` + +--- + +### 2. 消息管理 + +#### 获取消息列表 + +``` +GET /api/conversations/:id/messages?cursor=msg_001&limit=50 +``` + +| 参数 | 类型 | 说明 | +| -------- | ------- | ----------------- | +| `cursor` | string | 分页游标 | +| `limit` | integer | 每页数量,默认 50,最大 100 | + +**响应:** + +```json +{ + "code": 0, + "data": { + "items": [ + { + "id": "msg_001", + "conversation_id": "conv_abc123", + "role": "user", + "content": "你好", + "token_count": 2, + "thinking_content": null, + "created_at": "2026-03-24T10:00:00Z" + }, + { + "id": "msg_002", + "conversation_id": "conv_abc123", + "role": "assistant", + "content": "你好!有什么可以帮你的?", + "token_count": 15, + "thinking_content": null, + "created_at": "2026-03-24T10:00:01Z" + } + ], + "next_cursor": "msg_003", + "has_more": false + } +} +``` + +#### 发送消息(对话补全) + +``` +POST /api/conversations/:id/messages +``` + +**请求体:** + +```json +{ + "content": "介绍一下你的能力", + "stream": true +} +``` + +**流式响应 (stream=true):** + +``` +HTTP/1.1 200 OK +Content-Type: text/event-stream + +event: thinking +data: {"content": "用户想了解我的能力..."} + +event: message +data: {"content": "我是"} + +event: message +data: {"content": "智谱AI"} + +event: message +data: {"content": "开发的大语言模型"} + +event: done +data: {"message_id": "msg_003", "token_count": 200} +``` + +**非流式响应 (stream=false):** + +```json +{ + "code": 0, + "data": { + "message": { + "id": "msg_003", + "conversation_id": "conv_abc123", + "role": "assistant", + "content": "我是智谱AI开发的大语言模型...", + "token_count": 200, + "thinking_content": "用户想了解我的能力...", + "created_at": "2026-03-24T10:01:00Z" + }, + "usage": { + "prompt_tokens": 50, + "completion_tokens": 200, + "total_tokens": 250 + } + } +} +``` + +#### 删除消息 + +``` +DELETE /api/conversations/:id/messages/:message_id +``` + +**响应:** + +```json +{ + "code": 0, + "message": "deleted" +} +``` + +--- + +### 3. SSE 事件说明 + +| 事件 | 说明 | +| ---------- | ------------------------------- | +| `thinking` | 思维链增量内容(启用时) | +| `message` | 回复内容的增量片段 | +| `error` | 错误信息 | +| `done` | 回复结束,携带完整 message_id 和 token 统计 | + +--- + +### 4. 错误码 + +| code | 说明 | +| ----- | -------- | +| `0` | 成功 | +| `400` | 请求参数错误 | +| `401` | 未认证 | +| `403` | 无权限访问该资源 | +| `404` | 资源不存在 | +| `429` | 请求过于频繁 | +| `500` | 上游模型服务错误 | +| `503` | 服务暂时不可用 | + +**错误响应格式:** + +```json +{ + "code": 404, + "message": "conversation not found" +} +``` + +--- + +## 数据模型 + +### ER 关系 + +``` +User 1 ── * Conversation 1 ── * Message +``` + +### Conversation(会话) + +| 字段 | 类型 | 说明 | +| ------------------ | ------------- | --------------------- | +| `id` | string (UUID) | 会话 ID | +| `user_id` | string | 所属用户 ID | +| `title` | string | 会话标题 | +| `model` | string | 使用的模型,默认 `glm-5` | +| `system_prompt` | string | 系统提示词 | +| `temperature` | float | 采样温度,默认 `1.0` | +| `max_tokens` | integer | 最大输出 token,默认 `65536` | +| `thinking_enabled` | boolean | 是否启用思维链,默认 `false` | +| `created_at` | datetime | 创建时间 | +| `updated_at` | datetime | 更新时间 | + +### Message(消息) + +| 字段 | 类型 | 说明 | +| ------------------ | ------------- | ------------------------------- | +| `id` | string (UUID) | 消息 ID | +| `conversation_id` | string | 所属会话 ID | +| `role` | enum | `user` / `assistant` / `system` | +| `content` | string | 消息内容 | +| `token_count` | integer | token 消耗数 | +| `thinking_content` | string | 思维链内容(启用时) | +| `created_at` | datetime | 创建时间 | diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..f81ee87 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,42 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo + +.eslintcache + +# Cypress +/cypress/videos/ +/cypress/screenshots/ + +# Vitest +__screenshots__/ + +# Vite +*.timestamp-*-*.mjs + +test-results/ +playwright-report/ diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..4447ae8 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + GLM Chat + + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..1587aaf --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1347 @@ +{ + "name": "nano-claw", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nano-claw", + "version": "0.1.0", + "dependencies": { + "highlight.js": "^11.10.0", + "marked": "^15.0.0", + "vue": "^3.4.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.0", + "vite": "^6.0.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==", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.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==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", + "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", + "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", + "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", + "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", + "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", + "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", + "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", + "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", + "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", + "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", + "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", + "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", + "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", + "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", + "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", + "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", + "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", + "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", + "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", + "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", + "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", + "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", + "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", + "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", + "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.30.tgz", + "integrity": "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.30", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.30.tgz", + "integrity": "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.30", + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz", + "integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.30", + "@vue/compiler-dom": "3.5.30", + "@vue/compiler-ssr": "3.5.30", + "@vue/shared": "3.5.30", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.8", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.30.tgz", + "integrity": "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.30", + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.30.tgz", + "integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.30.tgz", + "integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.30", + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz", + "integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.30", + "@vue/runtime-core": "3.5.30", + "@vue/shared": "3.5.30", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.30.tgz", + "integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.30", + "@vue/shared": "3.5.30" + }, + "peerDependencies": { + "vue": "3.5.30" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.30.tgz", + "integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==", + "license": "MIT" + }, + "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/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "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/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/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmmirror.com/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "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/rollup": { + "version": "4.60.0", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.60.0.tgz", + "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", + "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.60.0", + "@rollup/rollup-android-arm64": "4.60.0", + "@rollup/rollup-darwin-arm64": "4.60.0", + "@rollup/rollup-darwin-x64": "4.60.0", + "@rollup/rollup-freebsd-arm64": "4.60.0", + "@rollup/rollup-freebsd-x64": "4.60.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", + "@rollup/rollup-linux-arm-musleabihf": "4.60.0", + "@rollup/rollup-linux-arm64-gnu": "4.60.0", + "@rollup/rollup-linux-arm64-musl": "4.60.0", + "@rollup/rollup-linux-loong64-gnu": "4.60.0", + "@rollup/rollup-linux-loong64-musl": "4.60.0", + "@rollup/rollup-linux-ppc64-gnu": "4.60.0", + "@rollup/rollup-linux-ppc64-musl": "4.60.0", + "@rollup/rollup-linux-riscv64-gnu": "4.60.0", + "@rollup/rollup-linux-riscv64-musl": "4.60.0", + "@rollup/rollup-linux-s390x-gnu": "4.60.0", + "@rollup/rollup-linux-x64-gnu": "4.60.0", + "@rollup/rollup-linux-x64-musl": "4.60.0", + "@rollup/rollup-openbsd-x64": "4.60.0", + "@rollup/rollup-openharmony-arm64": "4.60.0", + "@rollup/rollup-win32-arm64-msvc": "4.60.0", + "@rollup/rollup-win32-ia32-msvc": "4.60.0", + "@rollup/rollup-win32-x64-gnu": "4.60.0", + "@rollup/rollup-win32-x64-msvc": "4.60.0", + "fsevents": "~2.3.2" + } + }, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.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 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.30.tgz", + "integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.30", + "@vue/compiler-sfc": "3.5.30", + "@vue/runtime-dom": "3.5.30", + "@vue/server-renderer": "3.5.30", + "@vue/shared": "3.5.30" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..de93ba8 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "nano-claw", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.4.0", + "marked": "^15.0.0", + "highlight.js": "^11.10.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.0", + "vite": "^6.0.0" + } +} diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..ac3bc43 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1,4 @@ + + + G + diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..7ca7636 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,287 @@ + + + + + diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js new file mode 100644 index 0000000..0b2a704 --- /dev/null +++ b/frontend/src/api/index.js @@ -0,0 +1,124 @@ +const BASE = '/api' + +async function request(url, options = {}) { + const res = await fetch(`${BASE}${url}`, { + headers: { 'Content-Type': 'application/json' }, + ...options, + body: options.body ? JSON.stringify(options.body) : undefined, + }) + const data = await res.json() + if (data.code !== 0) { + throw new Error(data.message || 'Request failed') + } + return data +} + +export const conversationApi = { + list(cursor, limit = 20) { + const params = new URLSearchParams() + if (cursor) params.set('cursor', cursor) + if (limit) params.set('limit', limit) + return request(`/conversations?${params}`) + }, + + create(payload = {}) { + return request('/conversations', { + method: 'POST', + body: payload, + }) + }, + + get(id) { + return request(`/conversations/${id}`) + }, + + update(id, payload) { + return request(`/conversations/${id}`, { + method: 'PATCH', + body: payload, + }) + }, + + delete(id) { + return request(`/conversations/${id}`, { method: 'DELETE' }) + }, +} + +export const messageApi = { + list(convId, cursor, limit = 50) { + const params = new URLSearchParams() + if (cursor) params.set('cursor', cursor) + if (limit) params.set('limit', limit) + return request(`/conversations/${convId}/messages?${params}`) + }, + + send(convId, content, { stream = true, onThinking, onMessage, onDone, onError } = {}) { + if (!stream) { + return request(`/conversations/${convId}/messages`, { + method: 'POST', + body: { content, stream: false }, + }) + } + + const controller = new AbortController() + + const promise = (async () => { + try { + const res = await fetch(`${BASE}/conversations/${convId}/messages`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content, stream: true }), + signal: controller.signal, + }) + + if (!res.ok) { + const err = await res.json().catch(() => ({})) + throw new Error(err.message || `HTTP ${res.status}`) + } + + const reader = res.body.getReader() + const decoder = new TextDecoder() + let buffer = '' + + while (true) { + const { done, value } = await reader.read() + if (done) break + + buffer += decoder.decode(value, { stream: true }) + const lines = buffer.split('\n') + buffer = lines.pop() || '' + + let currentEvent = '' + for (const line of lines) { + if (line.startsWith('event: ')) { + currentEvent = line.slice(7).trim() + } else if (line.startsWith('data: ')) { + const data = JSON.parse(line.slice(6)) + if (currentEvent === 'thinking' && onThinking) { + onThinking(data.content) + } else if (currentEvent === 'message' && onMessage) { + onMessage(data.content) + } else if (currentEvent === 'done' && onDone) { + onDone(data) + } else if (currentEvent === 'error' && onError) { + onError(data.content) + } + } + } + } + } catch (e) { + if (e.name !== 'AbortError' && onError) { + onError(e.message) + } + } + })() + + promise.abort = () => controller.abort() + + return promise + }, + + delete(convId, msgId) { + return request(`/conversations/${convId}/messages/${msgId}`, { method: 'DELETE' }) + }, +} diff --git a/frontend/src/components/ChatView.vue b/frontend/src/components/ChatView.vue new file mode 100644 index 0000000..eb9425e --- /dev/null +++ b/frontend/src/components/ChatView.vue @@ -0,0 +1,303 @@ + + + + + diff --git a/frontend/src/components/MessageBubble.vue b/frontend/src/components/MessageBubble.vue new file mode 100644 index 0000000..408059c --- /dev/null +++ b/frontend/src/components/MessageBubble.vue @@ -0,0 +1,279 @@ + + + + + diff --git a/frontend/src/components/MessageInput.vue b/frontend/src/components/MessageInput.vue new file mode 100644 index 0000000..605bc0f --- /dev/null +++ b/frontend/src/components/MessageInput.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/frontend/src/components/SettingsPanel.vue b/frontend/src/components/SettingsPanel.vue new file mode 100644 index 0000000..2e11dc7 --- /dev/null +++ b/frontend/src/components/SettingsPanel.vue @@ -0,0 +1,379 @@ + + + + + diff --git a/frontend/src/components/Sidebar.vue b/frontend/src/components/Sidebar.vue new file mode 100644 index 0000000..fe104e2 --- /dev/null +++ b/frontend/src/components/Sidebar.vue @@ -0,0 +1,194 @@ + + + + + diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..44019e3 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './styles/highlight.css' + +createApp(App).mount('#app') diff --git a/frontend/src/styles/highlight.css b/frontend/src/styles/highlight.css new file mode 100644 index 0000000..536395f --- /dev/null +++ b/frontend/src/styles/highlight.css @@ -0,0 +1,67 @@ +/* highlight.js - GitHub Dark theme override for code blocks */ +@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap'); + +.hljs { + color: #e6edf3; + background: #0d1117; +} + +.hljs-comment, +.hljs-quote { + color: #8b949e; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-type { + color: #ff7b72; +} + +.hljs-string, +.hljs-addition { + color: #a5d6ff; +} + +.hljs-number, +.hljs-literal { + color: #79c0ff; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #ffa657; +} + +.hljs-function .hljs-title, +.hljs-title.function_ { + color: #d2a8ff; +} + +.hljs-variable, +.hljs-template-variable { + color: #ffa657; +} + +.hljs-attr, +.hljs-attribute { + color: #79c0ff; +} + +.hljs-selector-class { + color: #7ee787; +} + +.hljs-meta { + color: #79c0ff; +} + +.hljs-deletion { + color: #ffa198; + background: rgba(248, 81, 73, 0.1); +} + +.hljs-addition { + color: #aff5b4; + background: rgba(46, 160, 67, 0.15); +} diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..afa7734 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + server: { + port: 3000, + proxy: { + '/api': { + target: 'http://localhost:5000', + changeOrigin: true, + }, + }, + }, +}) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d60834d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[project] +name = "nano-claw" +version = "0.1.0" +description = "nano-claw Backend" +requires-python = ">=3.10" +dependencies = [ + "flask>=3.0", + "flask-sqlalchemy>=3.1", + "pymysql>=1.1", + "requests>=2.31", + "pyyaml>=6.0", +] + +[build-system] +requires = ["setuptools>=68.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +include = ["backend*"]